在C++中,基类中的"virtual"关键字是可选的吗?

3

我想要了解C++中"virtual"关键字的作用 - 考虑以下例子:

#ifdef USE_VIRTUAL
struct a {
  virtual void say_hi() { std::cout << "hello from a" << std::endl; }
};
#else
struct a {
  void say_hi() { std::cout << "hello from a" << std::endl; }
};
#endif

struct b : public a {
  void say_hi() { std::cout << "hello from b" << std::endl; }
};

int main( int argc, char** argc )
{
  a a_obj;
  b b_obj;
  a_obj.say_hi();
  b_obj.say_hi();
}

这个程序的输出是:
hello from a
hello from b

无论a::say_hi 是否声明为虚函数,都可以正确覆盖该函数。那么声明它为虚函数的作用是什么呢?


这种情况下,将函数声明为虚函数会告诉编译器在运行时动态绑定函数调用。这样就可以更加灵活地使用继承和多态性,并且能够正确地执行子类的函数。
1个回答

6

您没有使用多态性。多态行为仅影响指向基类的指针和引用,如下所示:

class A; class B1 : A; class B2 : A;

B1 x;
B2 y;
A  z;

A & a1 = x;  // static type A&, dynamic type B1&
A & a2 = y;  // static type A&, dynamic type B2&
A & a3 = z;  // static and dynamic type A&

现在访问a1a2a3的成员函数将涉及到多态性和虚拟分派。
然而,你必须在继承层次结构的顶部声明第一个函数为虚函数,即在A中!在你的例子中,如果没有virtual,就没有多态性,你总是调用对象相应静态类型的成员函数。要测试这一点,请添加另外几行:
a & bref = b_obj;
bref.say_hi();       // call b::say_hi() if virtual, a::say_hi if not
bref.a::say_hi();    // always call a::say_hi()

有趣的是...我(显然是错误的)期望编译器在我尝试覆盖非虚函数时会抱怨,就像C#一样。谢谢你为我澄清这个问题。 - Chris Vig
这是因为你没有覆盖,而是隐藏了基本函数。这是允许的,所以编译器假设你知道自己在做什么(确实,这不是C# :-)). 在C++11中,您可以添加override关键字来明确声明您的意图,在这种情况下,您甚至会得到一个漂亮的编译器错误提示。 - Kerrek SB
哈哈,好的。我显然需要回到我的C++教材了。谢谢! - Chris Vig
1
@Chris:在C#中,所有东西(有点)都是引用,因此您始终有资格进行动态调度。但是,如果通过对基本对象的引用来引用派生对象,那么多态性才是有趣的。在您的示例中,无论是否引用,以及是否虚拟,您始终会得到相同的输出。 - Kerrek SB

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