C++中`virtual`关键字在对象而非指针上的行为

4
我是一名有用的助手,可以为您翻译文本。
我正在尝试理解虚函数。
考虑以下代码:
#include <iostream>
#include <memory>
#include <vector>

class Animal 
{
public:
     virtual void eat() 
    {
        std::cout << "I eat like a generic animal.\n";
    }

};

class Wolf : public Animal 
{
public:
    void eat() 
    {
        std::cout << "I eat like a wolf!\n";
    }
};


int main() 
{

  Animal      a;
  Wolf        w;

  a.eat();
  w.eat();

使用 virtual 关键字,我获得以下输出结果:
I eat like a generic animal.
I eat like a wolf!

像它应该的那样。

但是如果我去掉了virtual关键字,我仍然得到同样的输出!从我对虚函数的初步理解来看,没有virtual,我本应该得到这个输出。

I eat like a generic animal.
I eat like a generic animal.

我是否漏掉了一些基本的东西?

我正在Linux上使用g++编译器。

3个回答

5

不,这是正确的行为。虚函数是引入多态性所必需的。要启用多态行为,您需要使用指针,如下所示:

 Animal * a = new Animal();
 Animal * w = new Wolf();

 a->eat();
 w->eat();

 <...>

 delete a;
 delete w;

以您现在的方式提供,行为是正确的,因为两个变量显然具有不同的类型。

4

多态通过识别实例实际引用的对象类型来工作。

在您的情况下,您实际的动物如下:

Animal      a;  //a is an animal.
Wolf        w;  //w is a wolf.

所以,你根本没有使用多态。

你需要做的更像是这样:

//Create a couple animal pointers.
Animal* a;
Animal* b;

//Create an animal instance and have a point to it.
a = new Animal();

//Create a wolf instance and have b point to it.
b = new Wolf();

//Calls Animal::eat as a is an animal.
a->eat();

//Calls Wolf::eat as a is a wolf.
b->eat();

请注意,您可以使用指针或引用来实现多态的使用。

这就是为什么在使用类类型时通常应该通过const-reference传递对象的原因。

//Will call Animal::eat or Wolf::eat depending on what animal was created as.
void foo(const Animal& animal) {
    animal.eat();
}

//Will always call Animal::eat and never Wolf::eat since this isn't a reference or
//a pointer.  Will also "slice" a Wolf object.
void foo(Animal animal) {
    animal.eat();
}

请注意,切片操作会将一个更具体的类(狼)不加区分地转换为该类的一个较不具体的副本(动物),这可能会非常误导和出乎意料。

谢谢你的回答,特别是第二点。虽然我不是专家,但看起来那似乎是一个缺陷。另外,为什么C++只通过指针和引用实现多态性呢? - smilingbuddha
1
@smilingbuddha:C++在编译时使用已知的最派生版本的函数,除非该函数是虚函数。然后它会在编译时检查。这不是C++没有进行多态性,而是因为你没有。你说“这是一只狼”,告诉它狼如何进食,它就会这样做。如果你想要动物行为,你必须告诉它将狼视为动物。这最好通过指针和引用来实现。 - Mooing Duck

1

即使没有虚拟,它仍然是一种方法。虚拟关键字允许在这种情况下实现多态行为:

Animal* wolf = new Wolf; // I eat like a wolf! (as long as eating is virtual)

通过使用虚拟关键字,您告诉编译器根据派生类型在运行时选择适当的实现进行调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接