我期待派生方法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;
}
答案 0 :(得分:2)
尽管有可能并且有时候是一个不错的选择,但大多数情况下,在构造函数中调用虚函数是个坏主意。只有你真正明白这意味着什么才能做到这一点。这同样适用于在析构函数中调用虚函数。
构建类B
的实例时,它首先构造一个A
。在此阶段,对象仅被标识为A
而不是B
的实例。所以在那个时候,对象有一个类A
的虚函数表。这意味着每个虚函数调用都将解析为类A
(或超类)的函数;适用相同的规则,就像您只构造A
的实例一样。
即使可以在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的结果是未定义的。