序言:
为了满足编译器的需要, 当类设计者没有显示的声明Default Constructor的时候, 编译器为满足编译程序的需要, 将会按照一定的规则自动生成Default Constructor。
一、编译器自动合成Default Constructor的四种情况
1、类的成员对象包含默认构造函数
2、继承的基类有默认构造函数
3、该类有虚函数, 需要初始化virtual function机制
4、该类继承自virtual base class, 需要配置virtual base class subobject机制
☆ 注意事项:若类中有其他构造函数, 编译器将不会生成default Constructor
1、类的成员对象包含默认构造函数
如果有多个类成员对象都要求用默认构造函数进行初始化操作? 那么在C++语言中将会按照“成员对象在class中的生命次序”来调用constructor, 但是编译器自动插入这些代码将会插入在explicit user code之前。
class Month{ public:Month(){ cout <<" Month Construtor\n";}};
class Eye{ public:Eye(){ cout <<" Eye Construtor\n";}};
class Ear{ public:Ear(){ cout <<" Ear Construtor\n";}};
class Head{
private:
Month month;
Eye eye;
Ear ear;
int mumble;
public:
Head(){cout <<"美美哒";
mumble = 2;
}
};
而Head的默认构造函数在经过编译器扩张后的C++伪代码如下
Head::Head(){
// 编译器自动拓展代码
month.Month::Month();
eye.Eye::Eye();
ear.Ear::Ear();
// explicit user code
cout <<"美美哒";
mumble = 2;
}
对以上的运行结果:
2、继承的基类有默认构造函数
如果类设计者把Derived设计成了没有构造函数的代码,但是他的Base类(基类)有默认构造函数, 那么编译器将会自动为其按照基类的顺序生成构造函数
3、该类有虚函数, 需要初始化virtual function机制
(1)、当class声明(或继承)一个virtual function的时候
(2)、class 派生自一个继承串链, 其中有一个或更多的virtual base classes
当出现以上情况的时候编译器将会自动合成默认构造函数, 去构造虚函数表的内容。
class Animal{
public:
virtual void run() = 0;
};
class Cat: public Animal{ public: void run(){cout << "Cat run\n";}};
class Pig: public Animal{ public: void run(){cout << "Pig run\n";}};
void run(Animal& a){
a.run();
}
void allrun(){
Cat c;
Pig p;
run(c);
run(p);
}
(1)一个virtual function table(在cfront中称为vtbl)会被编译器产生出来, 内存class的virtual functions地址
(2) 在每个class object中, 一个额外的pointer member(也就vfptr)会被编译器合成出来, 内含相关的virtual function table地址。
在allrun当中, Animal.run()的虚拟引发操作会被重新改写, 以使Animal的vptr和vtbl中的run条目
// Animal.run()的虚拟引发操作的转变
( *Animal.vptr[1] ) (&Animal)
☆ 1、代表run在virtual table中的固定索引
☆ &Animal代表需要交给“被调用的某个run()函数实体”的this指针
4、该类继承自virtual base class, 需要配置virtual base class subobject机制
Virtual base clas的实现法在不同的编译器之间有极大的差异。然而,每一种实现法的共通点在于必须使virtual base class 在其每一个derived class object中的位置,能够于执行期准备妥当。例如下面这段程序代码中:
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,public B {public:int k;};
//无法在编译时期决定(resolve)t出pa->x::i的位置
void foo(const A *pa){pa->i = 1024;}
int main() {
foo(new A);
foo(new C);
// ....
return 0;
}
编译器无法出定住foo() 之中“经由pa而存取的X::x”的实际偏移位置,因为pa的真正类型可以改变。编译器必须改变“执行存取操作”的那些码,使X::i可以延迟至执行期才决定下来.原先cfront的做法是靠“在derived class object的每一个virtual base classes中安插一个指针”完成。所有“经由reference或pointer 来存取一个virtual base class”的操作都可以通过相关指针完成.在我的例子中,foo() 可以被改写如下,以符合这样的实现策略:
void foo(const A* pa){pa->__vbcx->i = 1024;}
其中__vbcx表示编译器所产生的指针, 指向virtual base class X
二、总结
1、合成构造函数只会初始化member class object和base class subobjects, 所有其他nonstatic data member(整数, 指针, 字符, 浮点等)都不会被初始化
2、任何class 如果定义default constructor, 都会被合成出来---------这是错误的说法
3、编译器合成出来的默认构造函数会明确初始化每一个data member为默认值 -----这是错误的说法