深度探索C++对象模型——5_构造、析构、拷贝语义学

一般而言,类的data members应该被初始化,并且只在constructor中或者类的其他member functions中指定初值。其他任何操作都将破坏封装性,使class的维护和修改更加困难。
一个纯虚函数,能够被静态的调用,不能经由虚拟机制调用。
如果一个bc的虚析构函数被设置为纯虚函数,那么其所有dc都必须对虚析构函数进行定义。因为编译器会以静态调用的方式调用其每一个bc的析构函数。只要缺乏其中一个bc的析构函数的定义,就会导致链接失败。
不要将virtual destructor声明为pure。
不将一个函数声明为const,意味着此函数不能够获得一个const reference或const pointer。
将一个函数声明为const,意味着不能对data members进行更改。

1.无继承情况下的对象构造

一个object的生命是该object的一个执行期属性:

  • global object:生命和整个程序的生命相同
  • local object:存在于相应的程序块内
  • heap object:从被new运算符配置出来开始,到被delete运算符销毁。
    C++中的所有全局对象都被以“初始化过的数据对待”,故会生成一个default constructor,其初始化操作会延迟到程序启动再开始。
    观念上编译器会为类声明一个trivial default constructor,一个trivial destructor,一个trivial copy constructor以及一个trivial copy assignment operator。但是只有在被调用的时候才会被编译器真正的定义。

1.1 抽象数据类型

对类来说,无论是private或public存取层,或是member function的声明,都不会占用额外的对象空间。
除了第二章的那四种情况外需要合成默认构造函数外,普通的class来说默认的位语意已经足够,也不需要提供一个destructor,因为默认的内存管理方法也已经足够。
如果要将class中的所有成员都设定常量初始值,那么赋予一个explicit initialization list会更有效率些。
(如 Point local1={1,1,1};函数的activation record 被放进程序堆栈时,上述的初始化列表中的常量就可以放入到lcoal1的内存中)。
explicit initialization list带来的缺陷:

  • 1.只有当class members都是public,才奏效。
  • 2.只能指定常量,因为在编译期就可以被评估求值。
  • 3.由于编译器并没有自动施行,故初始化行为失败的可能性会高一些。
    (编译器层面提供了一个优化机制,将inline constructor的初始化操作设置成类似初始化列表中的操作,而不是赋值操作)。

1.2为继承做准备

虚函数的导入促使每一个类拥有一个虚指针。这个指针提供virtual的弹性,成本是:每一个object需要额外的一个word空间。
除了每一个object多负担一个vptr外,虚函数的导入也将引发编译器对class产生膨胀作用:

  • 1.所定义的constructor被附加了一些代码,以便将vptr初始化。这些代码被附加在任何bc constructor的调用之后,但必须在任何使用者供应的代码之前。
  • 2.合成一个copy constructor和一个copy assignment operator,其操作不再是trivial.如果一个class object被初始化或以一个dc object赋值,那么以位为基础的操作可能会对vptr带来非法设定。
    p205没怎么看明白

2.继承体系下的对象构造

编译器可能会做的扩充操作:p206->注意整理
析构函数是虚函数,也是会被静态的决议出来,而非通过虚拟机制。
自写的copy operator中需要检查自我赋值操作是否成功。

2.1 虚拟继承

由于虚拟继承的共享性的原因,传统的constructor扩充现象并没有用。
虚基类的构造函数被调用:只有当一个完整的class object被定义出来时,才会被调用;如果object只是某个完整object的subobject,就不会被调用。

2.2 vptr初始化语意学

构造函数的调用顺序:由根端到末端,由内而外。当bc的constructor执行时,derived实例还没有被构造出来。故在一个class的construct中,经由构造中的对象来调用一个virtual function,函数实例应该是此class中有作用的那个。
在构造函数中调用虚函数,实际上是以静态方式进行决议的,不用虚拟机制。除此外都必须经过虚拟机制来决定其归向。
故在继承体系中的一个class的constructor中正确调用虚函数,需要对虚函数的名单进行限制。通过对虚指针的初始化和设定操作。
虚指针的初始化在bc constructor调用操作之后,但是在程序员提供的members initialization list所列的members初始化操作之前。
令每一个bc constructor设定其对象的vptr,使它指向相关的virtual table 之后,构造中的对象就可以正确的变成“构造过程中所幻化出来的每一个class”的对象。
constructor的执行算法通常如下:

  • 1.在dc constructor中,所有的虚基类以及上一层基类的构造函数会被调用
  • 2.对象的vptr(s)被初始化,指向相关的虚表。
  • 3.member initialization list在constructor内扩展开来。(必须在vptr被设定后再做,避免在过程中有虚函数的调用)
  • 4.执行程序员锁提供的代码。
    vptr必须被设定的两种情况:
  • 1.当一个完整的对象被构建出来时。如果声明一个对象,则构造函数中必须设定其虚指针。
  • 2.当一个subobject constructor调用了一个虚函数(直接或间接)时。
    在一个class的构造函数的参数初始化列表中调用一个虚函数是否安全?
  • 实际而言,是安全的。因为vptr已经被编译器正确设定好了。
  • 语意上,不安全。因为函数本身还可能依赖未被设立初值的members。

3.对象复制语意学

如果不希望将一个object指定给另一个class object,只要将copy assignment operator声明为private并不提供定义即可。

  • 声明为private。则除了member functions以及该class的friends做赋值操作外,其他任何地点都不允许。
  • 不提供定义。则一旦member function或该class的friends企图拷贝,那么程序会在链接时失败。
    如果我们要支持的仅仅是简单的拷贝操作,那么默认行为不仅简单,而且有效率,没必要自己再提供一个copy assignment operator。只有在默认行为的语意不正确或不安全时,才需要。
    一个calss,在以下情况下财不会表现初bitwise copy语意时,才会合成一个赋值运算符:
  • 1.该class内含一个member object,并且其有一个赋值运算符。
  • 2.该class的基类有赋值运算符。
  • 3.该class声明了任何的虚函数。(因为虚指针不能直接拷贝右侧class object的vptr的地址)
  • 4.该class继承自一个虚基类。
    copy assignment operator有一个不严谨的情况,即它缺乏一个member assignment list(也就是平行于member initialization list的东西)。缺少这个,也就没法压制上一层bc的copy operators被调用。
    (p222,综合constructor的解决方案)
    尽量不要允许一个虚基类的拷贝,也尽量不要在任何虚基类中声明数据。

4.对象的效能

5.析构语意学

只有在下面两种情况下,编译器才会自动合成析构函数(与之前的不同):

  • 1.该class内含的member object有destructor
  • 2.该class继承的bc有destructor
    即使有虚函数也不会合成出来destructor
    一般而言,class的使用者没有办法检验一个local变量或heap 变量来知道它们是否被初始化。故构造函数是有必要的,否则抽象化的使用可能会有错误的倾向。
    destructor的调用顺序:
  • 1.destructor的函数本体首先被执行。
  • 2.如果class有member class objects,而后者有destructor。那么会按照声明顺序的相反顺序被调用。
  • 3.如果object内含一个vptr,现在被重新设定,使其指向适当的bc的virtual table、
  • 4.如果该class的bc有destructor,那么按照声明顺序的相反顺序被调用。
  • 5.如果虚基类有destructor,且目前class是最尾端的class,那么会按照原来的构造顺序的相反顺序被调用。
    目前对于destructor的一种最佳实现策略就是维护两份destructor实例:
  • 1.一个complete object实例,总是设定好vptr(s),并调用虚基类的destructor
  • 2.一个bc subobject实例,除非在destructor函数中调用一个virtual function,否则它绝不会调用虚基类的析构函数并设定vptr。
    在一个object析构过程中,会不断变成其bc的对象,在destrcutor中调用member functions时,对象的蜕变会因为vptr的重新设定而受到影响。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值