首页 > 代码库 > 编译器在构造函数里都做了些什么?
编译器在构造函数里都做了些什么?
我们都知道,C++是一种面向对象的语言,其中一个重要特性是多态性。多态性是通过基类指针指向子类对象,并通过这个基类指针调用子类函数(虚函数)来实现的。但是,看下面这个例子,我可以通过在构造函数里增加一行代码,从而使得这个多态不起作用!
看下面例子:
class Base{public: Base() { cout<<"Base::Base()"<<endl; } virtual ~Base() { cout<<"Base::~Base()"<<endl; } virtual void print() { cout<<"Base::print()"<<endl; }};class Derived: public Base{public: Derived() { //memcpy(this, 0, sizeof(Derived)); cout<<"Derived::Derived()"<<endl; memset(this, 0, sizeof(Derived)); } ~Derived() { cout<<"Derived::~Derived()"<<endl; } void print() { cout<<"Derived::print()"<<endl; }};int main(){ Base *b = new Derived(); b->print(); return 0;}
如果按照C++的多态特性,它应该输出:Derived::print(),但是真是如此吗?
下面,我们编译,运行,看看输出什么东西:
$ ./nopoly Base::Base()Derived::Derived()Segmentation fault (core dumped)
令人大跌眼镜,居然段错误!什么也没能输出,是什么原因导致的呢?其实是因为这么一行代码:
memset(this, 0, sizeof(Derived));
我们将vpt清为零了,因此,找不到Deried类的print函数,因此,便出现段错误了。
那么,我们回想一下,编译器到底在我们的构造函数里都干了些什么。
1. 记录在 member initialization list 中的 data members 初始化操作会被放进构造函数本身,并以members声明的顺序进行初始化;
2. 如果有一个member并没有出现在member initialization list 中,但它有一个default constructor,那么,该default constructor 必须被调用;
3. 在那之前,如果class object有virtual table pointer(s),它(们)必须被设定初值,并指向适当的virtual table(s)。
4. 在那之前,所有上一层的 base class constructors 必须被调用,并以base class 的声明顺序为次序(与member initialization list 中的顺序没有关联):
如果base class 被列于member initialization list中,那么任何明确指定的参数都应该传递过去;
如果base class 没有被列于 member initialization list 中,而它有default constructor (或default memberwise copy constructor ),那么就调用之。
如果base class 是多重继承下的第二或后继的base class ,那么this 指针必须有所调整。
5. 在那之前,所有 virtual base class constructors 必须被调用,从左到右,从最深到最浅:
如果class 被列于member initialization list 中,那么如果有任何明确指定的参数,都应该传递过去。若没有列于list 中,而class 有一个default constructor,也应该调用之。
此外,class 中的每一个virtual base class subobejct 的偏移量必须在执行期可被存取;
如果class object 是最底层的 class,其constructors 可能被调用;某些用以支持这个行为的机制必须被放进来。
编译器在构造函数里都做了些什么?