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

构造函数语意学

explicit使得能够制止"单一参数的constructor"被当做conversion运算符

2.1 Default Constructor 的构造操作

default constructors在需要的时候被编译器产生出来,被谁需要?做什么事情?
程序需要时并不会合成一个default constructor,只有当编译器需要它时才会合成,被合成出来的default constructor只执行编译器所需的行动

"带有Default Constructor" 的 Member Class Object

编译器需要为一个有default constructor的内含member object合成出一个default constructor,如果class A内含一个或一个以上的member class object,那么每个class A的每一个constructor必须调用每一个member classes的default constructor。编译器会扩张已存在的constructors,在其中安插一些代码,使得user code被执行之前,先调用default constructor。如果有多个class member object都需要进行初始化,C++语言要求以"member objects在class中的声明顺序"调用各个constructors。

default constructor、copy constructor、destructor、assignment copy operator都是以inline方式完成。如果函数太复杂不适合做成inline就会合成一个explicit non-inline static实例

"带有Default Constructor"的Base Class

如果一个子类的基类带有默认构造函数, 那么在合成子类的构造函数时, 会在其中插入对基类的默认构造函数会的调用代码, 这个代码在成员的默认构造函数调用代码之前. 即先初始化基类, 再按声明顺序初始化子 类成员.

"带有一个Virtual Function"的 Class

另外两种情况也需要合成默认构造函数:

  1. class声明(继承)一个virtual function
  2. class 派生自一个继承串链,其中有一个或更多的virtual base classes

下面两个扩张行为会在编译期间发生:

  1. 一个virtual function table (vtbl)会被编译器产生出来,内放class的virtual functions地址
  2. 在每一个class object中,一个额外的pointer member(vptr)会被编译器合成出来,内含相关之class vtbl的地址

编译器为每一个base class(或其derived class)object的vptr设定初值,放置适当的virtual table地址。对于class所定义的每一个constructor,编译器会安插一些代码来做这样的事,对于那些未声明constructor的classes,编译器会为它们合成一个default constructor,以便正确地初始化每一个class object的vptr。

"带有一个Virtual Base Class"的Class

必须使virtual base class在其每一个derived class object 中的位置,能够于执行期准备妥当

在合成的default constructor中,只有base class subobjects和member class objects会被初始化,所有其他的nonstatic data member(如整数、整数指针、整数数组等等)都不会被初始化,如果程序需要一个"把某指针设为0"的default constructor,那么提供它的人应该是程序员

2.2Copy Constructor 的构造操作

以一个object的内容作为另一个class object的初值:

  1. 对一个object做显式的初始化
  2. object被当作参数交给某个函数
  3. 当函数传回一个class object时

大部分情况下,当一个class object以另一个同类实例作为初值,copy constructor会被调用

Default Memberwise Intialization

  如果class没有提供一个explicit copy constructor,,必要时编译器会产生出来("必要"意指class不展现bitwise copy semantics时)
  拷贝手法是以所谓的default memberwise intialization完成的,也就是每一个内生的或派生的data member(例如一个指针或一个数组的值),从某个object拷贝到另一个object身上。不过不会拷贝其中的member class object。而是以递归的方式施行memberwise initialization

Bitwise Copy Semantics(位逐次拷贝)

  会拷贝每一个位(bit)
什么时候不要位逐次拷贝:

  1. 当类内含一个成员对象, 该成员对象中声明了一个copy 构造函数(不论是显式声明或是编译器合成)
  2. 类继承的基类中存在一个构造函数(不论是显式声明或是编译器合成)
  3. 类声明了一个或多个虚函数
  4. 当类派生自一个继承串连, 其中有一个或多个虚基类时

前两种情况编译器必须把member 或base class的拷贝构造函数调用操作安插到被合成的拷贝构造函数中

重新设定Virtual Table 的指针

  当一个class object以另一个相同的class object作为初值,都可以直接靠"bitwise copy semantics"完成
  当一个base class object以其derived class的object内容作初始化时,其vptr复制操作也必须保证安全,vptr不可以设定指向derived class的virtual table
  所以说合成出来的base class copy constructor会显式设定object的vptr指向base class 的virtual table,而不是直接从右手边的class object中将其vptr现值拷贝过来

处理Virtual Base Class Subobject

  virtual base class 的存在需要特别处理。一个class object如果以另一个有virtual base class subobject的object作为初值,那么也会使"bitwise copy semantics"失效,这是因为编译器必须让"derived class object中的virtual base class subobject位置"在执行期就准备妥当。"Bitwise copy semantics"可能会破坏这个位置,所以编译器必须在它自己合成出来的copy constructor中作出仲裁。编译器所产生的代码(用以调用virtual base class的default constructor、将derived class的vptr初始化,并定位出derived class中的virtual base class subobject)被安插在derived constructor间,成为其"先头部队"
  如果企图以一个derived class object作为base class object的初值,编译器必须判断"后续当程序员企图存取base class object的virtual base class subobject时是否能够正确地执行",为此编译器必须合成一个copy constructor,安插一些代码以设定virtual base class pointer/offset的初值,对每个members执行必要的memberwise初始化操作,以及执行其他的内存相关工作。

2.3程序转化语意学(Program Transformation)

显式的初始化操作

1.重写每一个定义,其中初始化操作会被剥离("定义"是指"占用内存"的行为)
2.class的copy constructor调用操作会被安插进去

参数的初始化

把一个class object当作参数传给一个函数或是作为一个函数的返回值,相当于以下形式的初始化操作:
X xx = arg; 其中xx代表形式参数或返回值,而arg代表真正的参数值

返回值的初始化

返回值如何从一个局部对象中拷贝过来
1.首先加上一个额外参数,类型是class object的一个reference,用来放置被"拷贝建构"而得的返回值
2.在return指令之前安插一个copy constructor调用操作,以便将欲传回之object的内容当做上述新增参数的初值

在使用者层面做优化

X bar(const T&y,const T&z)
{
  X xx;
      //......以y和z来处理xx
  return xx;
}
//由于这会要求xx被"memberwise"地拷贝到编译器所产生的_result之中
X bar(const T&y,const T&z)
{
  return X (y,z);//定义一个constructor
}

在编译器层面做优化

Named Return Value(NRV)优化:需要加上一个inline copy constructor,提供了重要的效率改善。
饱受批评的原因:

  1. 优化由编译器默默完成,而他是否真的被完成,并不十分清楚
  2. 一旦函数变得比较复杂,优化也就比较难以施行。许多程序员主张应该以"特殊化额constructor"策略取代之

一般而言面对"以一个class constructor作为另一个class constructor的初值"的情形,语言允许编译器有大量的自由发挥的空间。其利益当然是导致机器码产生有明显的效率提升。缺点则是你不能够安全地规划你的copy constructor的副作用,必须视其执行而定

Copy Constructor:要还是不要

copy constructor的应用迫使编译器多多少少对程序代码作部分优化,尤其是当一个函数以by value的方式传回一个class object,而该class有一个copy constructor(或定义或合成)时,无论在函数的定义还是在使用上将导致深奥的程序转化。此外,编译器将实施NRV优化。
注意正确使用memset()和memcpy(),它们都只有在classes不含任何由编译器产生的内部members如vptr时才能有效运行

2.4成员们的初始化队伍(Member Initialization List)

必须使用member initialization list:

  1. 当初始化一个reference member时
  2. 当初始化一个const member时
  3. 当调用一个base class的constructor,而它拥有一组参数时
  4. 当调用一个member class的constructor,而它拥有一组参数时

"初始化顺序"和"initialization list中的项目排列顺序"之间的外观错乱,会导致下面意想不到的危险
初始化列表中的项目被放在显示声明代码(explicit user code)之前

posted @ 2020-12-24 12:03  丸子球球  阅读(101)  评论(0编辑  收藏  举报