构造函数:深入理解C++对象模型第二章
- 我们知道一个变量需要经过初始化后才能使用,在C++中对象的初始化是由构造函数完成的。但有时我们写一个对象的时候,却可以省略不写构造函数,那么这个对象是如何初始化的呢?
这是由于C++编译器会自动为我们合成一个默认构造函数。当然C++编译器不是人这个复杂的高等生物,他只会遵循某种约定的规则来为我们合成这个默认构造函数。 - 下面我们回答一下下面这几个问题
- 1.编译器什么时候会为我们合成的额默认构造函数 首先对于个class X来说,如果用户没有显式定义一个构造函数,那么会有一个隐式的构造函数被申明出来。隐式申明出来的默认构造函数分为
nontrival和trival两种,trival构造函数(实际上不会被合成出来),第二种trival会被编译器合成出来。 - 2.哪些情况下会合成出这个默认构造函数呢? 有四种情况:
1.申明的类对象中含有其他对象,含有成员对象;
2.申明的类对象继承于一个有nontrival构造函数的对象;
3.存在虚函数 4继承自有虚函数的类 - 分析一下编译器制作出来的默认构造函数在每种情况下都做了啥工作。
1.调用成员的对象的默认构造函数;
2.调用父类的构造函数
3.合成virtual table 和vptr 并令vptr指向合成的vritual table
4.合成virtual table 和vptr 并令vptr指向合成的vritual table 注意:并不是如果一个class 没有申明默认构造函数就一定会申明出一个构造函数; 编译器申明的默认构造函数只满足编译器的需要,并不会对class中的每个data member赋值。 - 讲完了默认构造函数,接下来说一下拷贝构造函数,同样我们回答几个问题:
- 1.什么时候会用拷贝构造函数? 拷贝构造函数在程序中运用的位置有很多,主要在对对象做显示初始化的时候;当对象被当作一个参数给一个函数的时候;当函数返回一个对象的时候。
2.如果和默认构造函数一样,并没有提供拷贝构造函数,那会发生什么? 如果没有提供拷贝构造函数,内部以一种默认成员初始化的方法完成成员数据的赋值。和默认构造函数一样,编译器也把拷贝构造函数分为trival和nontrival两种。
- 3.上面我们说了默认构造函数区分trival和nontrival的方法,那么拷贝构造函数是如何区分trival和nontrival的呢? 区分拷贝构造函数trival和nontrival语义的方法是根据该类是否表现出 bitwise Copy(位拷贝语义)展开的。如果一个class 展现出bitwise的语义,那么编译器并不会为其合成一个拷贝构造函数。
- 4.现在我们回答下一个问题,什么时候class不展现出一个bitwise的语义呢?
1.当class含有成员对象,且后者申明了一个拷贝构造。
2.当class继承自一个class是,且后者有拷贝构造。
3.当class含有虚函数时;
4.当class派生自含有虚函数的类时。 - 总结:需要编译器合成默认构造函数和需要编译器合成拷贝构造函数的条件基本相同。在拷贝构造函数中做的事也和默认构造函数中做的事基本相同。
- 5.前面分析过编译器合成的函数做了哪些事,其中调用父类和包含对象的默认构造函数或拷贝构造函数很好理解,但对于class virtual table和vptr的设定有必要展开说明一下。
- Class对象复制的时候如果展现出bitwise的语义,那么编译器就不会合成一个拷贝复制函数去执行相应的命令。但是如果一个class对象含有虚函数的时候在同类对象复制的时候符合bitwise copy 语义学,但为基类对象复制子类对象的时候,则不存在bitwise的语义。上章中,book作为library的子类,在对thing1做拷贝构造的时候,会发生对象切割的现象。
- book thing2;
- Library_material thing1=thing2;
- 在对象thing1中vptr指向的virtual
table当然是library的virtual table。thing2中vptr指向book的virtual table
因此不符合bitwise copy的语义。 - 到这里为止,已经把类中拷贝构造函数讲完了。下面我们来看一下把对象作为参数传递给函数和函数传递出参数时会发生什么。
- 参数的初始化:在函数进入后,在栈中实际建立一个局部对象,返回前析构对象。
返回值的初始化:首先在函数内不增加一个额外参数,用来放置拷贝构建而得到的返回值,在return语句前增加一个copy
constructor的调用操作将欲传回的值作为新增参数的值。 - 在参数初始化和返回值初始化上面,编译器针对这个问题做出一个NRV优化(以result参数取代named return value)【要进行NRV优化必须定义拷贝构造函数】
- 初始化列表:记住 初始化列表中所有语句在explicit user code之前,且初始化列表中代码的执行的跟参量在内存中先后相关。