【问题标题】:Calling a virtual function on a reference在引用上调用虚函数
【发布时间】:2026-01-12 08:25:02
【问题描述】:

在下面的代码中,为什么最后一次调用 c 上的 eat() 会返回“动物 b 正在吃东西。" ?据我了解,c 是对派生类 Dog 的实例 b 的引用,而 eat() 是虚函数。所以它应该返回“A dog b is eating.”

#include <string>
#include <iostream>

using namespace std;

class Animal
{

protected:
    string name;

public:
    Animal( string _name ):
    name(_name)
    {

    }

    virtual void eat()
    { 
        cout << "An animal " << name << " is eating." << endl;
    }
};

class Dog : public Animal
{

public:

    Dog( string _name ):
    Animal(_name)
    {

    }

    void eat()
    {
        cout << "A dog " << name << " is eating." << endl;
    }
};

int main( int argc , char ** argv )
{
    Animal a("A");
    a.eat();

    Dog b("b");
    b.eat();

    Animal & c = a;
    c.eat();

    c = b;
    c.eat();

    return 0;
}

这是输出:

An animal A is eating.

A dog b is eating. 

An animal A is eating. 

An animal b is eating.

【问题讨论】:

  • 我在您的代码中没有看到引用 d
  • @taocp,抱歉是 c,不是 d。我已经修好了。
  • 12 秒内 3 个答案.... :)

标签: c++ inheritance virtual


【解决方案1】:

因为您无法重新绑定引用。一旦你初始化它们,它们的名字总是指你初始化它们的对象。

一个对象可以有一个名字,例如Animal a("A"); 创建一个 Animal 类型的对象,并引入一个名称 a 来引用这个对象。

另一方面,引用引入名称而不引入对象(我们不考虑临时对象):

Animal& c = a; // a new name `c` which refers to the same object as `a`

// another (evil) example:
Animal& c = *(new Animal("C")); // `new` introduces an object without name
                                // `c` now refers to this object

关于作业:

Animal & c = a;
// the name `c` is now equivalent to the name `a`

c = b; // equivalent to `a = b;`

最后一个赋值获取b 引用的对象,并将其Animal 类型的子对象复制到c 引用的对象。因为ac 是等价的,所以a 指的是同一个对象。因此,a.name 设置为"B"

虚函数调用 c.eat() 当然是在动态类型为 Animal 的 id 表达式 (c) 上运行 - 与 a 相同的类型 - 因此,调用 Animal::eat 而不是 @987654339 @。

【讨论】:

    【解决方案2】:

    引用是对象的别名。将引用绑定到对象后(并且必须在初始化时发生),您对引用所做的操作将在被引用的对象上完成

    特别是,你不能重新绑定一个已经绑定到一个对象的引用并让它引用另一个对象。因此,下面的赋值(因为这是一个赋值,而不是初始化):

    c = b;
    

    等价于:

    a = b;
    

    因为c 是对对象a 的引用。上面的赋值导致slicing,这不是你想要的:c 不会是绑定到b 的引用,但它仍然是绑定到a 的引用,b 有已分配。

    【讨论】:

    • 切片在这个特定的上下文中是什么意思?是不是说只有派生类Dog的对象的“Animal”部分被复制到a了?
    • @takwing:是的,确实如此。另请参阅this article 以获得更详细的说明
    【解决方案3】:

    为了利用虚函数提供的动态多态性(在运行时区分派生类和基类),您需要通过基类指针或引用来访问派生类对象。

    我已经注释掉了你的代码中可能发生混淆的地方:

    int main( int argc , char ** argv )
    {
    
        Animal a("A");
        a.eat();
    
        Dog b("b");
        b.eat();
    
        // Make a reference (alias) to Animal object and set it to the object a. 
        // From this point on, whenever you write c, think "a".
        Animal & c = a;
        // So, this is a.eat()
        c.eat();
    
        // This is a = b (Animal = Dog): DANGER! SLICING! Here, the assignment operator
        // slices the derived object and only assigns the base object "part" (remember, 
        // read "a", where you see "c" in your code): 
        // a.operator=(const A& b)
        c = b;
        // a.eat() = a is object of type A, so naturally, here you call A::eat()
        c.eat();
    
        return 0;
    }
    

    【讨论】:

      【解决方案4】:

      一旦你绑定了一个引用,你就不能重新绑定它,所以你必须使用指针而不是引用:

      Animal *c = &a;
      c->eat();
      
      c = &b;
      c->eat();
      

      现在它将完全按照您的意愿工作。

      【讨论】:

        【解决方案5】:
        Animal & c = a;
        c.eat();
        
        c = b; ///^^^
        c.eat();
        

        在 C++ 中,引用一旦被初始化就不能重新绑定到其他对象。 c 仍然是对象a 的别名,它是Animal,因此,您看到了预期的输出。

        【讨论】: