为什么在派生类中调用一个非虚成员函数的影子函数,却没有调用基类的成员函数?

125
假设我们在Visual C++ 2010中有以下情景:
#include <iostream>

using namespace std;

struct Base {
    void Display() {
        cout << "Base: Non-virtual display." << endl;
    };
    virtual void vDisplay() {
        cout << "Base: Virtual display." << endl;
    };
};

struct Derived : Base {
    void Display() {
        cout << "Derived: Non-virtual display." << endl;
    };
    virtual void vDisplay() {
        cout << "Derived: Virtual display." << endl;
    };
};

int main() {
    Base ba;
    Derived de;

    ba.Display();
    ba.vDisplay();
    de.Display();
    de.vDisplay();
};

理论上,这个小应用程序的输出应该是:
基类:非虚拟显示。 基类:虚拟显示。 基类:非虚拟显示。 派生类:虚拟显示。
因为基类的Display方法不是虚拟方法,所以派生类不应该能够覆盖它。对吗?
问题是,当我运行这个应用程序时,它打印出来的是:
基类:非虚拟显示。 基类:虚拟显示。 派生类:非虚拟显示。 派生类:虚拟显示。
所以要么我不理解虚拟方法的概念,要么在Visual C++中发生了一些奇怪的事情。
这是什么解释?

修改为 de.Base::Display() 后,您绝对会有一个非虚拟显示基类。 - v.oddou
3个回答

179

你有点误解了。

在这种情况下,派生类中同名的方法将隐藏父类的方法。你可能认为如果不是这种情况,尝试创建与基类非虚方法同名的方法应该会抛出一个错误。但实际上允许这样做,并且如果像你现在这样直接调用该方法,它将被正确地调用。

然而,由于它不是虚拟的,因此不会使用C++方法查找机制来支持多态性。例如,如果你创建了一个派生类的实例,但通过指向基类的指针调用了你的'Display'方法,那么将调用基类的方法,而对于'vDisplay'则会调用派生类的方法。

例如,尝试添加这些行:

Base *b = &ba;
b->Display();
b->vDisplay();
b = &de;
b->Display();
b->vDisplay();

观察输出,效果与预期相同:

基类:非虚显示。
基类:虚显示。
基类:非虚显示。
派生类:虚显示。


同样,正如我所说的,您还可以使用作用域解析语法从派生实例中调用(非虚拟)基本方法。 - v.oddou
1
所以,只要确保,我可以在基类中定义一个方法,并在派生类中覆盖它,无论是否将其声明为虚拟。唯一的区别是,如果一个基指针指向一个派生类对象,那么调用该方法将调用基类的方法(如果它不是虚拟的),并调用派生类的方法(如果它是虚拟的)。这是正确的吗?还有其他区别吗? - SexyBeast
仅更改头文件是否足够?还是必须使用“virtual”关键字编译源代码? - Paul Knopf
@PaulKnopf 在类声明之外的定义上,不允许使用 'virtual' 关键字。所以你不需要在 cpp 文件中添加它。当然,你需要重新编译。 - sje397
1
有没有好的实践或者好的理由来重写一个非虚方法? - Joshua Segal
显示剩余2条评论

38
是的,你有一点误解:
纯虚函数
virtual void fun1() = 0 -> 必须在派生类中重写
虚函数:
virtual void fun2() -> 可以被重写
普通函数:
void fun3() -> 不要重写它
为了实现运行时多态性,你需要在C++中重写虚函数。

11

我认为更好的方式是从静态绑定和动态绑定的角度来看待它。

如果方法是非虚拟的(与Java不同,C++中默认为非虚拟),则该方法在编译时将绑定到其调用者,而无法知道在运行时将指向哪个实际对象。因此,唯一重要的是变量类型,即“Base”。


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