《inside the c++ object model》读书笔记 之五 构造,解构,拷贝语意学

...引子:每一个derived class destructor会被编译器扩展,以静态调用的方式调用其"每一个virtual base class"以及"上一层base class的destructor",因此,只要缺乏任何一个base class destructor的定义,就会导致链接的失败,并且,比较好的设计模式是,不要把virtual destructor声明为pure.
  一般而言,把所有的成员函数声明为virtual function,然后在靠编译器的优化操作把不必要的额外virtual invocation去处,不是一个好的设计理念.
  关于虚拟规格中的const:不把函数声明为const,意味着该函数不能获得一个const reference或const pointer,声明一个函数为const,该函数就不能够修改data member.比较简单的办法就是不用const.

5.1"无继承"情况下的对象构造:
  在C之中,global被视为一个"临时性定义",因为它没有明确的初始化操作,一个"临时性的定义"可以再程序中发生多次,只留下一个实体,被放在程序 data segment中一个"特别保留给未初始化之global objects使用"的空间,该技术被称为BBS.
  C++中,并不支持"临时性的定义",这是因为class构造行为的隐含应用的缘故,global在C++中被视为完全定义(它会阻止第二个,或更多定义).C和C++的一个差异就在于,BBS data segment在C++中的相对地不重要,C++中的所有全局对象都被当作"初始化过的数据"来对待.

...抽象数据类型:
  如果要对class中的所有成员都设定常量初值,那么给与一个explicit initialization list会比较高效,在local scope也是如此,但是initialization list会带来三个缺点:
  1)只有当class members都是public时,才有效.
  2)只能指定常量,因为它们在编译时期就可以被评估求值.
  3)由于编译器并没有自动施行之,所以初始化行为的失败可能会比较高一些.

5.2 继承体系下的对象人构造:
  constructor可能内带大量的隐藏码,因为编译器会扩充每一个constructor,扩充程度视class T的集成体系而定,一般而言编译器所做的扩充操作大约如下:
  1)记录在member initialization list中的data members初始化操作会被放进constructor的函数本身,并以members的声明顺序为顺序.
  2)如果有一个mmeber(该member为 member class)并没有出现在member initialization list中,但它有一个default constructor,那么default constructor必须被调用.
  3)在那之前,如果class object有virtual table pointers.它们必须被设定初值,指向适当的virtual tables.
  4)在那之前,所有上一层的base class constructors必须调用,以base class的声明次序为顺序(与member initialization list中的顺序无关):
    a)如果base class被列于member initialization list中,那么任何明确指定的参数都应该传递过去.
    b)如果base class没有被列于member initialization list 中,而它有default constructor(或default memberwise copy   constructor),那么就调用之.
    c)如果base class是多重继承下的第二个或是后继的base class,那么this指针必须有所调整.
  5)在那之前,所有virtual base class constructors必须被调用,从左到右,从最深到最浅:
    a)如果class 被列于member initialization list中,那么如果有任何明确指定的参数,都应该传递过去.若没有列于list中,而class有一个default constructor,也应该调用之.
    b)此外,class中的每一个virtual base subobject的偏移量(offset)必须在执行期可存取.
    c)如果class object是最底层的class,其constructors可能被调用某些用以支持这个行为的机制必须被放进来.

...关于vptr的设定:
  设定vptr是编译器的责任,vptr的设定是在,base class constructors调用操作之后,但是在程序员供应的码或是"member initialization list中所列的members初始化操作"之前.
  一般而言,constructor的执行算法是:
  1)在derived class constructor中,所有的virtual base classes以及上一层base class的constructors被调用.
  2)上述完成之后,对象中的vptr被初始化,指向相关的virtual table.
  3)如果有member initialization list的话,将在constructor体内扩展开来,这必须在vptr被设定之后才进行,以免有一个virtual member function被调用.
  4)最后,执行程序员所提供的码.

  在class的constructor的member initialization list中调用该class的一个虚拟函数,实际而言,就vptr是安全的,因为vptr已经设定好,但是并不推荐这种做法,因为该函数还可能依赖为被设立初值的members.  
  在需要给一个base class constructor提供参数时,这种情况下在"class的constructor的member initialization list中"调用该class的虚拟函数,就是不安全的了,因为此时vptr不是尚未设定好,就是被设定指向错误的class.

5.3 对象复制语意学:
  当我们设计一个class,并以一个class object指定给另一个class object时,我们有三种选择:
  1)什么也不做,因此得以实施默认的行为.
  2)提供一个explicit copy assignment operator.
  3)明确地拒绝把一个class object指定给另一个class object.
  如果选择3)的话不准将一个class object指定给另一个class object,那么只要将copy assignment operator声明为private,并且不提供其定义即可.将其设定为private,我们就不再允许在任何地点进行赋值操作,不提供函数定义,则一旦某一个member function或friend企图影响一份拷贝,程序在链接时就会失败.

  一个class对于默认的copy assignment operator,在以下情况下不会表现出bitwise copy语意:
  1)当class内带一个member object,而其class有一个copyassignment operator时.
  2)当一个class的base class有一个copy assignment operator时.
  3)当一个class声明了任何virtual functions(我们一定不能拷贝右端class object的vptr地址,因为它可能是一个derived class object)时.
  4)当class 继承自一个virtual base class(不论此base class有没有copy operator)时.

注:copy assignment operator并不表示bitwise copy semantics是nontrivial,实际上,只有nontrivial instances才会被合成出来.

  事实上,copy assignment operator在虚拟继承情况下行为不佳,需要小心设计和说明,许多编译器甚至并不尝试取得正确的语意,它们在每一个中间的copy assignment operator中调用一个base class instance,于是造成virtual base class copy assignment operator的多个实体被调用,C++ Standard说:并没有规定那些代表virtual base class的subobject是否该被"隐喻定义的copy assignment operator"指派(赋值)内容一次以上.
  一个以语言为基础的解决办法是为copy assignment operator提供一个附加的"member copy list",有一个方法可以保证most-derived class会引发virtual base class subobject的copy 行为,就是在derived class的copy assignment operator函数实体最后,明确调用那个operator,然而,这并不能省略subobjects的多重拷贝,但却可以保证语意的正确.
  另一个解决方案是,把virtual subobject拷贝到一个分离的函数中,并根据call path条件化地调用它.
  建议尽可能不要允许一个virtual base class的拷贝操作,并且,尽量不要在virtual base class声明数据.

5.4解构语意学:
  如果class没有定义destructor,那么只有在class 内带的member object(或是class自己的base class)拥有destructor的情况下,编译器会自动合成出一个来,否则,destructor会被视为不需要,也就不需要被合成.
  一个有程序员定义的destructor被扩展的方式类似constructor被扩展的方式但是顺序相反:
  1)destructor的函数本身被执行,也就是说vptr会在程序员的码执行之前被重设(reset).
  2)如果class拥有member class objects,而后者拥有destructors,那么它们会以其声明顺序的相反顺序被调用.
  3)如果object内带一个vptr,那么首先重设(reset)相关的virtual table.
  4)如果有任何直接的(上一层)nonvirtual base clas拥有destructor,它们会以其声明顺序相关的顺序被调用.
  5)如果有任何virtual base classes拥有destructor,而当前讨论的这个class是最尾端(most-derived)的class,那么它们会以其原来的构造顺序的相反顺序被调用.

  就像constructor一样,目前对于destructor的一种最佳实现策略就是维护两份destructor实体:
  1)一个complete object实体,总是设定好vptr,并调用virtual base class destructor.
  2)一个base class subobject实体;除非在destructor函数中调用一个virtual function,否则它绝不会调用virtual base class destructors并设定好vptr.
  一个object的生命结束与其destructor开始执行之时,由于每一个base class destructor都轮番被调用,所以derived object实际上变成了一个完整的object.当我们在destructor中调用member function时,对象的蜕变会因为vptr得重新设定(在每一个destructor中,在程序员提供的码执行之前)而受到影响.

原文地址:https://www.cnblogs.com/suiyu/p/2736336.html