深度探索C++对象模型【第二章】

版权声明:本文为博主原创文章,转载请注明出处-- https://blog.csdn.net/qq_38790716/article/details/84888626

1. 默认构造函数

1.在C++构造函数中,编译器会做你很多不知道的事,而引入关键词explicit,正是为了防止构造函数被隐式转换

2.当编译器需要的时候,才会合成默认构造函数,而且被合成出来的构造函数只执行编译器所需的行动;如果是程序本身需要,则承担责任的是设计类的人

3.下面讨论四种必须为类合成默认构造函数的情况:

  • 如果一个类没有任何的构造函数,但它内含一个成员对象,而这个对象有默认构造函数,那么这个类的隐式默认构造函数就被认为是有用的,编译器就需要为该类合成一个默认构造函数,合成操作只在构造函数被调用时才发生
  • 如果一个没有任何构造函数的类派生自一个带有默认构造函数的基类,则这个派生类的默认构造函数被认为是有用的,并因此被合成出来
  • 类未声明任何构造函数,但声明或继承了一个虚函数,则编译器会为它合成一个默认的构造函数,以便正确的初始化每一个类对象的vptr
  • 类未声明任何构造函数,但继承自一个虚基类,编译器会为它合成一个默认构造函数

4.合成的构造函数之所以被合成出来,是因为有自己的任务:

  • 调用成员对象或基类的默认构造函数
  • 为每一个对象初始化其虚函数机制或虚基类机制

5.编译器合成的默认构造函数不会为其每一个数据成员赋初始值,只有基类子对象和成员类对象会被初始化

2. 拷贝构造函数

1.拷贝构造函数被调用的三种情形:

  • 对一个对象做显示的初始化操作,X xx = x;
  • 对象被当做参数交给某个函数, foo(xx);
  • 函数传回一个类对象, X foo_bar();

2.如果类未提供一个显示的拷贝构造函数,则当类对象以相同的类的另一个对象作为初值,其内部是通过对每个成员进行默认初始化的手法完成的,即从一个对象拷贝到另一个对象中

class String {
public:
	//...
private:
	char *str;
	int len;
};
//出现上述情况
String noun("book");
String verb = noun;
//相当于对每个成员默认初始化
verb.str = noun.str;
verb.len = noun.len;

3.标准C++把拷贝构造函数分为trivial(无用的)和nontrivial(有用的),只有nontrivial的实例才会被合成于程序中

4.决定一个拷贝构造函数是否为trivial的标准在于类是否展现出所谓的"bitwise copy semantics"(位逐次拷贝),如果类展现出了所谓的位逐次拷贝,则不需要编译器合成默认的拷贝构造函数,下面简述4中情况不展现出位逐次拷贝:

  • 当类内含一个成员对象而后者的类声明中有一个拷贝构造函数(不管该拷贝构造函数是被显示声明亦或被编译器合成)
  • 当类继承自一个基类而后者存在于一个拷贝构造函数(不管该拷贝构造函数是被显示声明亦或被编译器合成)
  • 当类声明了一个或多个虚函数时
  • 当类继承自一个继承串链,其中有一个或多个虚基类

重点讨论后两点

5.对于位逐次拷贝,执行地是对指针的拷贝,是一种浅拷贝,拷贝过后的指针指向的同一块内存,当一个指针被释放后,另一个指针指向为空,当该指针也被释放时,会导致内存泄漏;当存在虚函数时,就存在vptr,对vptr的浅拷贝显然不正确,可能不指向正确的vbtl,所以也就解释了为什么有虚函数就需要合成的拷贝构造函数,对vptr进行正确的初始化
关于浅拷贝与深拷贝

6.编译器对于虚拟继承的支持,让派生类对象中的虚基类子对象位置在执行期就准备妥当,维护"位置的完整性"是编译器的责任。位逐次拷贝可能会破坏这个位置,因此编译器必须合成一个拷贝构造函数

7.总结:如果没有显示定义的拷贝构造函数时,编译器将合成拷贝构造函数,以应对一些特殊的拷贝操作使之拷贝正确

3. 程序转化

1.在严谨的C++用词中,“定义”是指“占用内存的行为

2.将类对象作为参数传递给函数或作为函数的返回值,导入一个临时对象,并调用拷贝构造函数将其初始化,然后再将该临时对象交给函数,在函数使用完之后,会自动调用destructor进行销毁临时对象

3.在编译器层面做优化:NRV优化NRV优化如今被视为标准C++编译器的一个义不容辞的优化操作,所谓的NRV优化即保存返回值的变量,不再在函数中创建一个临时对象,以result参数将该临时对象取代,并调用默认构造函数
关于NRV优化,个人觉得讲解很好的一篇文章理解NRV优化

4.NRV优化提供了重要的效率改善,但还是饱受批评,主要原因如下:

  • 优化由编译器完成,而它是否真的被完成,并不十分清楚
  • 一旦函数变得比较复杂,优化就变得比较难以施行
X xx0(1024);
X xx1 = X(1024);
X xx2 = (X) 1024;

上述三个初始化操作在语意上一致,但第二、三个操作中,将一个临时性的对象设以初值1024,同时将该临时变量以拷贝构造的方式作为显示对象的初值,这样在构造过程中创建的临时变量,会增加效率负担

5.如果类含有虚函数或内含一个virtual base class,那么使用memcpy()或memset()会导致”被编译器产生的内部成员“的初值被改写(vptr)

6.当一个函数以传值的方式传回一个类对象,而该对象有一个拷贝构造函数时,会引发NRV优化

4. 成员初始化

1.使用成员初始化列表的四种情形:

  • 当初始化一个reference member(引用成员)时
  • 当初始化一个const member(常量)时
  • 当调用一个base class的构造函数时,而它拥有一组参数
  • 当调用一个member class 的构造函数,而它拥有一组参数

2.在上述四种情况下,不使用成员初始化列表,程序可以被正确编译,但效率不高

例:当类中含有string成员时,会构造一个临时对象并将其初始化,使用赋值运算符指定给类成员,这样效率不高

而当使用成员初始化列表时,内部扩张为string(0),省去了上述一系列繁琐的操作

3.成员初始化列表中的项目顺序是由类中的成员声明顺序决定的,不是由成员初始化列表中的排列顺序决定的

4.成员初始化列表的操作会在任何显示定义操作之前

5.使用存在于构造函数中的一个成员,而不要使用存在于成员初始化列表中的成员;也可以在构造函数体内使用成员函数设定一个成员的值

----------------------------------get关键词--------------------------------------------------------

  1. 编译器合成default constructor的四种情况
  2. 不展现Bitwise copy Semantics的四种情况
  3. 对于Bitwise copy Semantics的理解
  4. NRV优化
  5. 成员初始化列表

猜你喜欢

转载自blog.csdn.net/qq_38790716/article/details/84888626