编译器何时会自动产生一个default constructor?编译器自动合成的default constructor将会初始化nonstatic member data?程序设计者什么时候需要显式地提供default constructor?程序设计者显式地提供了default constructor,编译器就不再做什么了吗?那么,到底Default Constructor的构造操作是如何运转的呢,请带着上面的问题走进这篇博文。
一、Default Constructor在“需要的时候”由编译器产生
标题强调“在需要的时候”default constructor由编译器产生。那么,为什么强调“在需要的时候”?是谁需要?先看下面一段代码:
#include <iostream>
class Foo
{
public:
int val;
Foo *pnext;
};
void main()
{
Foo bar;
if (bar.val || bar.pnext)
{
std::cout << "hello world" << std::endl;
}
std::cin.get();
}
在这个例子中,我们常认为,编译器将生成default constructor,val和pnext将会被初始化为0。但运行这段代码,13行代码出现了异常:
Run-Time Check Failure #3 - The variable ‘bar’ is being used without being initialized.
正如所料,虽然我们的程序的语意是要求Foo有一个default constructor,可以将它的两个members初始化为0,但编译器并没有自动生成一个default constructor,更别说初始化它们。上述代码程序设计者没有提供任何constructor,而且编译器也没有合成,但我们程序的语意是需要default constructor的。那么,问题来了:
到底是“谁需要的时候?”
答案是:编译器需要的时候,编译器才会自动合成;而不是程序需要的时候,程序需要的时候是由程序设计者手动编写代码,显然上述例子:编译器不需要,而程序需要的时候,但程序设计者并没有手动提供任何构造函数,因此,异常的发生并不奇怪。
请记住,在编译器需要的时候,编译器才会合成一个default constructor。此外,被合成出来的default constructor只执行编译器所需要的行动。也就是说,即使编译器为上述片段合成了default constructor,那个constructor也不会把val和pnext初始化为0。为了让程序正确的运行,class Foo的程序设计者必须提供一个显式的default constructor,将两个members进行适当地初始化。
也许编译器并没有为上例代码合成default constructor,但C++ Standard已经修改了这种[ARM的说法]说法,虽然行为事实上仍然是相同的:
对于class X,如果没有任何user-declared constructor,那么会有一个default constructor被隐式声明出来……一个隐式被声明出来的default constructor将是一个没用的constructor……
那么,什么情况下implicit default constructor是有用(nontrivial)的呢?
二、“带有Default Constructor”的Member Class Object
理解标题最简单粗暴的做法就是直接看程序,完整的代码片段如下:
#include <iostream>
class Foo
{
public:
Foo(){ _x = 0; };
Foo(int x) :_x(1){}
int getX() { return _x; }
private:
int _x;
};
class Bar
{
public:
Foo foo;
char* str;
};
void main()
{
Bar bar;// Bar::bar必须在此处初始化
// Bar::foo是一个member object,而其class Foo拥有一个default constructor,符合本小节主题
std::cout << bar.foo.getX() << std::endl; // 输出0
std::cout << bar.str << std::endl; // 发生异常
std::cin.get();
}
在第27行打断点,F10逐语句执行,程序执行到断点处,正常输出0;执行到第29行处异常发生。为什么异常会发生呢?这就引出本节的主题:
如果一个class没有任何的constructor(比如:Bar),但它内含一个member class object(Bar::foo),而后者有default constructor,那么这个class的implicit default constructor就是“nontrivial”(有用的),编译器需要为该class合成一个default constructor,不过该constructor只有在被调用时才会被合成。
既然有用的default constructor都被合成了,那么为什么Bar::str没有被初始化,编译器任由异常发生???这就是需要特别注意的地方:
再次强调,编译器合成的default constructor只满足编译器的需要,不满足程序的需要(str的初始化是程序员的任务,不是编译器的责任)。被合成的default constructor只内含必要的代码(调用Foo的default constructor),而不会完成程序员的任务(初始化Bar::str),因此异常会发生。被合成的default constructor看起来可能会像酱紫:
// Bar 的default constructor可能会被合成酱紫
// 为member object foo调用class Foo的default constructor
inline // 注意是inline
Bar::Bar()
{
// C++伪码
foo.Foo::Foo();
// 注意这里并没有对Bar::str进行初始化操作
}
假设Bar::str的初始化由程序员完成,像酱紫:
Bar::Bar()
{
str = 0;
}
虽然程序的需求已经得到满足,但是编译器的需求仍然没得到解决(还需要初始化member object foo),由于default constructor已经被显式地定义出来,因此编译器不会再合成第二次,而是这么做:编译器会在explicit user code(这里是str = 0;)之前安插调用member object的default constructor的代码。酱紫:
// 扩张后的default constructor
// C++伪码
Bar::Bar()
{
foo.Foo::Foo();// 附加上的compiler code
str = 0;
}
如果class Bar内含一个或一个以上的member class object,后者都含有default constructor,而且它们都要求constructor的初始化操作,那么编译器该怎么做呢?如果Bar的设计者没提供default constructor,那么编译器合成一个,并在该constructor体内内含必要代码(调用member class object 的default constructor以完成初始化)。如果Bar的设计者显式地提供了default constructor,那么编译器只需要在default constructor中安插必要的调用member class object的default constructor的代码(按照声明的顺序),并且都在explicit user code之前。
三、“带有Default Constructor”的Base Class
类似的,如果类A继承自类B,B含有default constructor,A没有任何的constructor,那么编译器会合成一个有用的default constructor,该constructor用以调用基类的default constructor。酱紫由编译器合成的派生类的default constructor和一个“被显式提供的default constructor”没有什么差异,显式提供的default constructor由编译器安插调用基类default constructor的代码。
四、“带有一个Virtual Function”的Class
另有两种情况,也需要合成default constructor:
1、class声明(或继承)一个virtual function;
2、class派生自一个继承串链,其中有一个或多个virtual base classes。
为什么编译器为带有virtual function的class合成default constructor?在缺乏user声明的constructors的情况下。这是因为,任何带有virtual function的class在编译期间会发生下述行为:
1、 一个virtual table(在cfront中被称为vtbl)会被编译器产生,该虚表存放class中的virtual function的地址。
2、每一个class object,一个额外的“指向虚表的指针”(也就是vptr)会被编译器合成出来,内含相关class 虚表(vtbl)的地址。
请看下面这个类,
class Test
{
public:
virtual void func();
private:
int _i;
};
该类的内存布局为:
为了让虚拟机制发生功效,编译器必须为每一个类或其派生类object的vptr设定初值,放置适当的virtual table地址。对于那些没有声明任何constructors的classes,编译器会为它们合成一个default constructor,以便正确地初始化每一个class object的vptr。如果程序设计者显式地提供过了default constructor呢?哈哈,这个问题都遇到多少次了,编译器在user提供的default constructor中安插必要的代码呗!!!
五、“带有一个Virtual Base Class”的Class
又成为“钻石继承”,这里不再赘述,见《深度探索C++对象模型》P46
六、总结
我觉得也没什么好总结的了,因为Lippman的总结字字珠玑,全干货,截图如下:
参考文献:《深度探索C++对象模型》侯捷 译
本文详细探讨了在哪些情况下编译器会自动生成Default Constructor,以及编译器生成的Default Constructor如何运作。编译器仅在必要时生成Default Constructor,如成员类对象有默认构造函数、基类有默认构造函数、类包含虚函数或虚拟基类等情况。同时,文章强调了编译器生成的构造函数仅满足基本需求,程序员仍需显式初始化某些成员变量。
1万+

被折叠的 条评论
为什么被折叠?



