为什么在编译期间选择了这个虚方法的实现?

时间:2013-10-18 18:23:30

标签: c++ inheritance polymorphism override virtual

我尝试过运行以下内容:

struct B;
struct C;
struct A{
    A() { f(this);}
    virtual A* f(A* a) {
        cout << " A::f(A)" <<endl;
        return a;
    }
    void h() { cout << typeid(*this).name() << endl;}
    void g(B* b);
};

struct B:A{ 
    B() { f(this); }
    virtual A* f(A* a) {
        cout << "B::f(A)" << endl;
        return a;
    }

    virtual B* f(B* b) {
        cout << "B::f(B)" << endl;
        return b;
    }

    virtual C* f(C* c) {
        cout << "B::f(C)" << endl;
        return c;
    }
};

struct C: B{};

void A::g(B* b) { cout << typeid(*this).name() << endl; f(b);};

int main(){
    B* b = new B();
    cout << "------" << endl;
    C* c = new C();
    cout << "------" << endl;
    c->g(b);
    return 0;
}

请注意,g()是非虚拟的,因此在编译期间选择它。

运行时,我得到以下输出:

A::f(A)
B::f(B)
------
A::f(A)
B::f(B)
------
1C
B::f(A) <----- Notice this

请注意,最后一行似乎调用了f(),好像它是动态绑定的,但只调用了A知道的方法f()(我认为这与g()静态绑定的事实有关)。 我期望发生的是获得B :: f(B)。

为什么f()的调用是在编译时计算的g()?

4 个答案:

答案 0 :(得分:2)

A::g不知道B又引入了f重载。事实上,它选择对f(A*)进行虚拟通话,因为它是该地区唯一已知的f

虚拟调度仅由(不可见)-1st参数(this)完成,而不是由任何其他参数完成。因此函数B::f(B*)不参与虚拟链。因此,选择实际的f(A*) - 即B::f(A*)

调用虚函数并不意味着最佳匹配签名是在运行时选择的,而只是实际的类。签名是在编译时选择的(除了返回类型)。

答案 1 :(得分:1)

重载与虚拟多态无关。只有A::f(A*)是虚拟的并且是动态分派的。函数B::f(B*)完全不相关。

答案 2 :(得分:0)

您对基类中的派生类有依赖关系。由于A::gA的成员,因此使用A的vtable进行编译。它不是在编译时“计算”的。您创建的依赖项使其查看vtable的错误部分。为了澄清,A只定义了f(A*)。它对f(B*)一无所知,因此无法对其进行调用。

如果你在实际代码中遇到这种情况,你真的需要考虑你的设计。

答案 3 :(得分:0)

C ++ 11添加了override关键字,以帮助您在此处理问题。

如果您认为virtual方法会覆盖基本virtual方法,请将关键字override附加到其中。

在这种情况下,当您将B更改为包含:

virtual B* f(B* b) override {
    cout << "B::f(B)" << endl;
    return b;
}

编译器会抱怨并告诉您B* f(B*)不会覆盖其父项中的任何方法。你在这里做的是介绍一个新的重载,一个完全不同的函数恰好与方法A* f(A*)具有相同的名称。

具有相同名称的方法参与重载解析,但不要覆盖彼此virtual

因为它具有相同的名称,您认为它是override - 但实际上并非如此。一旦你意识到B* f(B*)A* f(A*)无关,那么所有其他事情都会非常有意义。

A中的重载解析检查了f(b)的选项,只看到一个选项,确定它匹配(因为A*参数与B*兼容),然后调用它。此时检查virtual函数表,找到正确的overrideB::f(A*),唯一的覆盖),然后调用它。