为什么在编译时选择此虚拟方法的实现

Why is this virtual method's implementation chosen during compile time?

本文关键字:虚拟 方法 实现 选择 编译 为什么      更新时间:2023-10-16

我尝试运行以下内容:

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() 中的调用是在编译时计算的?

A::g不知道

B又引入了一个重f。事实上,它选择对f(A*)进行虚拟呼叫,因为它是那个地方唯一已知的f

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

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

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

您依赖于基类中的派生类。 由于A::gA的成员,所以它是使用A的vtable编译的。 它不是在编译时"计算"的。 您创建的依赖项使其查看 vtable 的错误部分。 澄清一下,A只定义了f(A*)。 它对f(B*)一无所知,所以它没有办法称呼它。

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

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函数表,并找到正确的override(这是B::f(A*),唯一的覆盖),然后调用该表。