【问题标题】:Why is this virtual method's implementation chosen during compile time?为什么在编译时选择这个虚拟方法的实现?
【发布时间】:2013-10-18 18:23:29
【问题描述】:

我已尝试运行以下命令:

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

【问题讨论】:

    标签: c++ inheritance polymorphism overriding virtual


    【解决方案1】:

    A::g 不知道B 引入了f 的另一个重载。事实上,它选择虚拟呼叫f(A*),因为它是那个地方唯一已知的f

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

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 直截了当。出于某种原因,我认为参数是协变的(这显然是错误的)。
      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        C++11 添加了override 关键字来帮助您处理这里的问题。

        当您认为 virtual 方法会覆盖基本 virtual 方法时,请将关键字 override 添加到它。

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

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

        编译器会抱怨并告诉您B* f(B*) 不会覆盖其父级的任何方法。您在这里所做的是引入了一个新的 overload,这是一个完全不同的函数,恰好与方法 A* f(A*) 同名。

        同名方法参与重载解析,但不覆盖virtually。

        因为它具有相同的名称,所以您认为它是 override - 但实际上并非如此。一旦您意识到B* f(B*)A* f(A*) 无关,那么发生的其他一切都是完全合理的。

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-05-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-09-07
          • 1970-01-01
          相关资源
          最近更新 更多