首页 > 代码库 > 深度探索C++对象模型 第二章构造函数语意学

深度探索C++对象模型 第二章构造函数语意学

 

在使用C++时,常常会好奇或者抱怨,编译器为我们做了什么事呢? 为什么构造函数没有为我初始化呢?为什么我还要写默认构造函数呢?

2.1 Default Constructor 的构造操作

如果没有声明默认构造函数,编译器会在需要的时候帮我们产生出来。 为了避免在多个地方被需要导致重复,则编译器将产生的构造函数声明为inline方式。

class Foo {public:Foo(), Foo(int) };

class Bar {public: Foo foo;char *str;}

Bar bar; // 则会调用Bar的默认构造函数,同时该默认构造函数会初始化member object成员,调用其构造函数

编译器在Bar中插入代码:

inline Bar::Bar() { foo.Foo::Foo();}; 但不会初始化str,需要程序员来进行初始化;

【重点】编译器的行为是:如果class A内涵一个或者一个以上的member class objects,那么class A 的每一个constructor必须调用每一个member classes的default constructor。 如果多个member class object是都需要初始化,则根据在class中声明的顺序进行初始化;

如果已经有明确声明的构造函数,则不会在生命默认的构造函数,但仍需要在所有声明的构造函数中调用包含或者继承的member class objects相应的构造函数;如果没有明确的构造函数,则需要合成default constructor,并调用相应的member class objects的constructor。

2)带有virtual Function的class

编译器有两个扩张行为:每一个class生成一个vtable,包含virtual function地址;其次每个class object中包含一个pointer member(vptr)并指向vtable;  【注意】 每一个包含virtual函数的class都有一个vtable(自己带有virtual或者从父类继承过程的virtual,都需要创建一个vtable,和父类的vtable独立,这样才能够保证运行时多态,子类指针或者引用转换为父类的引用或者指针,还是指向该类对象所指向的vtable(只是指针所指向内存布局不同)

以下4中情况会造成编译器为未声明的classes合成一个default constructor。 (implicit nontrivial default constructors),被合成的constructors只用来满足编译器的需要。

1)member object(包含)或者base class(继承)的default constructor(先初始化父类base constructor,其次是member object)

2)为每一个object初始化其virtual function机制或virtual base class机制(多重机制仅保留一份)

如 class X{public: int i;}

class A:public virtual X{public:int j}  class B:public virtual X{public:int d} 

class C:public A,public B{public :int k}

void foo(const A *pa) (pa->i = 1024)  转换为  pa -> _vbcX->i = 1024, 则_vbcX需要在virtual继承过程的default constructor中初始化,保留仅有一份;

误解:

1)任何class如果没有定义default constructor,就会被合成出一个(只有被需要时才会)

2) 编译器合成出来的default constructor会显示设定"class 内每一个data member的默认值“ (显然不会)

2.2 Copy Constructor 的构造操作

出现的三种情景:

1) X x; X xx = x;

2)  foo(X x) 函数参数

3)  X foo() { return x}  函数返回值  => 转换为  foo(X &result) { result = x ; return }

default Memberwise Initialization

一种data memer(一个指针或者数组)从某一个object拷贝到另一个object上(bitwise copy semantics 位逐次拷贝)

但不会拷贝member class object(而是以递归的方式调用该member class object的copy constructor,所谓的memberwise initialization)

四种情况不会bitwise copy semantics

1) 当class 中内含一个member object ,而该object内含copy constructor(包含合成的)

2)class 继承一个base class

3)class 声明了一个或者多个virtual functions

4)class 派生自一个继承链只能够,其中一个或者多个virtual base classes时

主要介绍第三种情况,第四种情况太复杂,暂时不考虑

Bear 包含virtual function, 继承自 ZooAnimal(包含virtual function),都包含virtual void draw(){}

Bear yogi;

Bear winnie = yogi; 

ZooAnimal franny = winie;  // 如果实施bitwise copy semantics,则franny中的vptr则等于winie中的vptr;

则franny.draw() 将调用Bear中的draw,显然错误。 包含vptr的sliced强制拷贝,保留父类的vptr指针,则franny中的vptr仍指向ZooAnimal中的vtable。(补图)

总结:object自己生成的data member不会被赋值(通过copy or assignment constructor),这样可以保证自身独立性

2.3 程序转换语义学

分贝针对copy constructor存在的三种情况进行介绍,如何在编译层面进行转换,不同编译器实现不同(采用何种优化方式)。主要介绍返回值时如何调用copy constructor,这样方便程序员进行优化,主题提出的思路是添加函数第一个参数来替代函数的返回值。函数的返回值编译器自动优化为NRV。

T foo() {return t};  => 编译器优化 void foo(T &result) { X tmp; result = tmp; }  =>   程序员优化  void foo(T &result) { result.x = x;} 

2.4 成员们的初始化列表  member initialization list

适应情况:

1) 当初始化一个reference member时 必须

2) 当初始化一个const member 时,必须

3) 当调用一个 base class 的constructor,而它拥有一组参数时,可以提高效率

4) 当调用一个member class的constructor,额它拥有一组参数时,可提高效率

class Word {

String _name;

int _cnt;

public:

Word(){_name = 0; _cnt = 0;}

}

则会转换为,Word构造函数中首先调用Sting的构造函数初始化_name,然后调用String的拷贝构造函数对0初始化,并生成临时变量String tmp,然后调用赋值构造函数operator=,将值赋给_name,最终调用destructor来销毁_tmp。

而通过member initialization list,则将直接可以调用String(0)实现_name构建。

Word():_name(0) { _cnt = 0;}  

初始化列表顺序和代码位置

初始化列表的顺序和data member声明的顺序相关,和列表中初始化顺序无关,并且编译器会生成代码,将初始化列表中代码插入到constructor中,并且一定在explicit user code之前。

int i;

int j;

Word():j(val),i(j) {} 

出现问题,先初始化i,而j未知,导致error。