正在调用基本方法而不是构造函数的派生方法

时间:2014-01-16 00:36:54

标签: c++ inheritance

我期待派生方法get()将从A的构造函数中调用。想知道为什么这不会发生?

方法get()在基类中是虚拟的,因此派生类B将覆盖此方法。

#include <iostream>
using namespace std;

class A {
protected:

    virtual int get() {
        cout<<" in A::get "<<endl;
        return 0;
    }
public:
    A() {
        get();
    }
};

class B : public A {
public:
    B():A() {}

protected:  
    int get() override{
        cout<<"in B:get() "<<endl;
        return 0;
    }
};


int main() {
A *a;  a = new B();
return 0;
}

2 个答案:

答案 0 :(得分:2)

尽管有可能并且有时候是一个不错的选择,但大多数情况下,在构造函数中调用虚函数是个坏主意。只有你真正明白这意味着什么才能做到这一点。这同样适用于在析构函数中调用虚函数。

构建类B的实例时,它首先构造一个A。在此阶段,对象仅被标识为A而不是B的实例。所以在那个时候,对象有一个类A的虚函数表。这意味着每个虚函数调用都将解析为类A(或超类)的函数;适用相同的规则,就像您只构造A的实例一样。

如需完整说明,请阅读When my base class's constructor calls a virtual function on its this object, why doesn't my derived class's override of that virtual function get invoked?

即使可以在B::get()(例如:http://ideone.com/sF3411)的构造函数中调用函数A,这也是未定义的行为,因为{{1}的构造函数执行时,A指针尚未指向this的实例,如果你愿意,它只是“准备中”。链接的代码可能有效,但它是一个黑客并暴露未定义的行为。

答案 1 :(得分:1)

除了来自leemes的C ++ FAQ链接,C ++标准阻止它被调用,我将引用该标准。

以下说明,可以在构造函数/析构函数中调用虚拟成员函数,但它们充当“非虚拟”,您已经在C ++ FAQ中知道了。

  

可以调用成员函数,包括虚函数(10.3)   在施工或毁坏期间(12.6.2)。当一个虚函数   从构造函数或从构造函数直接或间接调用   析构函数,包括在构造或破坏期间   class的非静态数据成员,以及调用的对象   适用于建造或销毁的对象(称之为x),   被调用的函数是构造函数中的最终覆盖   析构函数的类,而不是在更派生的类中重写它。   如果虚函数调用使用显式类成员访问   (5.2.5)和对象表达式是指x的完整对象   或该对象的基类子对象之一,但不是x或其中一个   基类子对象,行为未定义。

它实际上还说,当从一个指针(对自身)调用虚函数时,该指针的类型不是自身的直接基类(在多重继承中),行为是未定义的。

示例(来自标准)

struct V {
  virtual void f();
  virtual void g();
};
struct A : virtual V {
  virtual void f();
};
struct B : virtual V {
  virtual void g();
  B(V*, A*);
};
struct D : A, B {
  virtual void f();
  virtual void g();
  D() : B((A*)this, this) { }
};
B::B(V* v, A* a) {
  f(); // calls V::f, not A::f
  g(); // calls B::g, not D::g
  v->g(); // v is base of B, the call is well-defined, calls B::g
  a->f(); // undefined behavior, a’s type not a base of B
}

以上规则适用于其他动态绑定内容,包括typeid,这意味着您不能使用typeid来区分基础构造函数中的派生类类型。

  

可在构造期间使用typeid运算符(5.2.8)   毁灭(12.6.2)。在构造函数中使用typeid时(包括   用于非静态的mem-initializer或brace-or-equal-initializer   数据成员)或在析构函数中,或在被调用的函数中使用   (直接或间接)来自构造函数或析构函数,如果是   typeid的操作数是指正在构建的对象或   destroy,typeid产生表示的std :: type_info对象   构造函数或析构函数的类。如果typeid的操作数指的是   正在建造或破坏的物体和静电类型   操作数既不是构造函数也不是析构函数的类,也不是一个类   它的基础,typeid的结果是未定义的。