c++ 2.1 编译器何时创建默认构造函数
我们通常会说当生命一个 class 时,如果我们不为该 class 指定一个 constructor,那么编译器会替我们实现一个 connstructor,那么这种说法一定对吗?
事实上,这是不对的。这个不是我说的,是深入理解C++对象模型说的,原话是:
C++新手一般有两个常见的误解:
- 任何 class 如果没有定义 default constructor,就会被合成出一个来。
- 编译器合成出来的 default constructor 会明确设定 "class 内每一个 data member 的默认值。
下面分别讨论上述四种合成 constructor 的情况:
情况一:一个 class 内含一个 member object,且其 member object 拥有 default constructor
class Foo { public: Foo(), Foo( int ) ... }; class Bar { public: Foo foo; char* str; }; //不是继承,是包含 void foo_bar(){ Bar bar; //Bar::foo 必须在此处初始化 //译注:Bar::foo是一个member object,而其 class Foo 拥有default constructor,符合本小节主题 if( str ) { } ... }
此时 class Bar 内含一个 member object Foo,并且 Foo 有 default constructor Foo(),那么编译器就会为 Bar 合成一个 default constructor,看起来像这样:
//Bar 的 default constructor 可能会这样合成 //被 member foo 调用 class Foo 的 default constructor inline Bar::Bar(){ //C++伪码 foo.Foo::Foo(); }
再一次请你注意,被合成的 default constructor 只满足编译器的需要,而不是程序的需要,为了让这个程序片段能够正确执行,字符指针 str 也需要初始化,那么程序员可能会这么做:
Bar::Bar() { str = 0; } //程序员定义的 default constructor
此时编译器还需要初始化 member object foo,但是由于 default constructor 已经被程序员明确定义出来了,编译器没办法合成第二个。编译器采取的行动是:“如果 class A 内含一个或一个以上的 member class object,那么 class A 的每一个 constructor 必须调用每一个 member classes 的 default constructor”,编译器会向用户程序员的 constructors 前面插入必要的 default constructor。插入后可能像这样:
//插入后的 default constructor //C++伪码 Bar::Bar(){ foo.Foo::Foo(); //插入的 compiler code str = 0; //explicit user code }
如果有多个 class member objects 都要求初始化操作,将如何做呢?C++会按照 “member objects 在 class 中的声明次序“ 来调用各个 consructors。这一点由编译器完成。并且如果某个 member object 的 default constructor 被程序员定义过了,它照样会被按顺序调用。如:
//程序员对 Snow_White 类所写的 default constructor,显示初始化一个 member object Snow_White::Snow_White() : sneezy( 1024){ mumble = 2048 }
它会被扩张为:
Snow_White::Snow_White() : sneezy( 1024) { //插入 member class onject //调用其 constructor dopey.Dopey::Dopey(); sneezy.Sneezy::Sneezy(1024); bashfun.Bashfun::Bashful(); //explicit user code mumble = 2048; }
情况二:带有 “default constructor” 的 base class
情况三:带有一个 “virtual Function” 的 class
如果一个 class 声明或继承一个 virtual function,并且缺乏由用户声明的 constructors,编译器会详细记录合成一个 default constructor 的必要信息。如:
class Widget{ public: virtual void filp() = 0; ... } void filp( const Widget& widget ) { widget.filp(); } //假设 Bell 和 Whistle 都派生自 Widget void foo(){ Bell b; Whistle w; filp( b ); filp( w ); }
下面两个扩张步骤在编译期间发生:
- 一个 virtual function table (在 cfront 中被称为 vtbl)会被编译器产生出来,内放 class 的 virtual functions 地址。
- 在每一个 class onject 中,一个额外的 pointer member(也就是 vptr)会被编译器合成出来,内含相关的 class vtbl 的地址。
//widget.filp() 的虚拟引发操作(virtual invocation)的转变 // (* widget.vptr[ 1 ] )( &widget )
- 1 表示 filp() 在 virtual table 中的固定索引
- &widget 代表要交给 “被调用的某个 filp() 函数实体” 的 this 指针
情况四:带有一个 ”virtual base class“ 的 class
class X { public: int i; }; class A : public virtual X { public: int j; }; class B : public virtual X { public: double d; }; class C : public A, pulic B { public: int k; }; //无法在编译使其决定(resolve)出pa->X::i 的位置 void foo( const A* pa) { pa->i = 1024; } main() { foo( new A ); foo( new C ); // ... }
//可能的编译器转变操做,中间加了一层 void foo( const A* pa ) { pa->__vbcX->i = 1024; }
- 编译器合成出来的 default constructor 会明确设定 "class 内每一个 data member 的默认值