title: “ 《深度探索C++对象模型》笔记(5)\t\t”
tags:

  • 笔记
    url: 1451.html
    id: 1451
    categories:
  • C/C++
    date: 2018-12-30 14:30:38

看到章节名以后联想第二章,和自己想的差不多。这张主要是更细化讲解,具体的“扩充”过程 不要把虚析构定义为pure。这个和google编程规范里写得一样,接口类应该全都是纯虚函数及一个虚析构函数,不应具有构造函数。 不要指望编译器去识别没有有派生类重写虚函数而主动去掉virtual,应自己决定好是否有virtual 虚函数不要有const 析构函数的建立并不是见到“virtual”就会自动建立,和构造不一样,只有在成员对象或基类有析构时才会被建立出来,也就是构造和析构不一定会成对出现,非必要时不要自己建立析构。

构造、析构、拷贝语意学

无继承情况下的对象构造

Plain Ol’ Data,C风格的struct结构体定义的数据结构,编译器不会对此类型添加构造和析构、拷贝函数(会用位拷贝代替)。与C的区别,对于全局变量C认为是临时性定义,而C++认为都是初始化过的数据。 全局对象程序启动时初始化,结束时销毁 当所有数据成员都为public时,可以通过Point p = {1.0,1.0,1.0};方式构建同时初始化(显示初始化列表),其对应值只能为常量(编译时确定值)。

继承体系下的构造

当我们定义一个object如:T object;如果T有一个constructor(不管是trival还是nontrival),它都会被调用,constructor调用伴随了(下面按扩充后代码的顺序写得):

  1. 有virtual base class,虚基类的构造函数必须被调用,从左往右,从深往浅: 类如果在成员初始化列表中,则显式指定的参数都应该传过去,不在初始化列表中的类,调用其默认构造函数 类中每一个虚基类子对象(subobject)的偏移位置(offset)必须在执行期是可取的 类对象是最底层的派生类,其构造函数可能被调用,用以支持这一行为的机制需要被执行
  2. 有base class,基类的构造函数必须被调用,以base class声明顺序为顺序; 如果基类在初始化列表中显式指定,那么任何显式指定的参数都应该传进去 如果基类不在初始化列表中,但有默认构造函数的话,那么就调用这个默认构造函数构造基类 如果基类是多重继承下的第二或后继的基类,那么this指针要做调整
  3. 如果类对象有虚表指针,初始化指向适当的虚函数表
  4. 函数初始化列表中的成员变量的初始化操作会被放入构造函数中
  5. 如果有成员不在初始化列表中,并且其有默认构造函数,那么默认构造函数必须被调用

虚拟继承

虚拟继承涉及到virtual base subobject共享性,不能重复构建,针对上面的第一步,进行扩充时要特别处理。 会对每一个虚基类增加一个标识符(__most_devried)是否有多个派生类,当第一次构建时为false进行构建,并改为true,后续再遇到构建时将不再重复构建。同样的针对析构函数也要做处理,避免重复释放。实际上就是继承链的最底层派生类负责对虚拟基类的object的构建,中间层不会进行重复构建。 上面的方式会增加过多的判断,为了增加这些判断编译器的代码会更加复杂,因此有些新的编译器针对构造函数分成两份,一个完整object,一个针对subobject。完整的无条件调用所有virtual base构造,subobject不调用任何虚拟基类构造。

vptr

对于vptr的指定:在base class constructiors调用操作之后,在程序员代码或初始化列表中所列的成员初始化操作之前,此时将vptr指向对应的vtbl

对象复制语意学

一个类对于默认的拷贝运算符函(=号)数,在以下情况,不会表现出按位拷贝的语意。

  1. 当类中含一个成员对象,而这个object的class有一个赋值运算符函数
  2. 当类的基类有一个赋值运算符函数
  3. 当一个类声明了任何虚函数,这时候不可以拷贝右边类对象的vptr,因为其可能是派生类对象
  4. 类继承自虚基类,不论这个base class是否有拷贝函数

性能上来说,在虚拟继承时,可以保证虚拟基类拷贝后只有一个subobject,但并未要求这个subobject只被指派一次内容(初始化一次),也就是拷贝过程中虚拟基类会被多次初始化,因为多个派生类的“=”操作均会被执行,(如其int成员会被多次赋值为0),只影响初始化,subobject不会重复。

析构

只有在member object或base class拥有析构时,编译器才会合成出一个,否则不会被合成也不会被调用(delete时不会执行操作),单纯自身拥有虚函数是不会创建析构函数的。 构造与析构不一定要成对 析构函数的执行顺序一般是

  1. 析构函数的函数本体首先被执行
  2. 如果类有类对象成员,而类对象有析构函数,那么他们会以其声明顺序的相反顺序被调用
  3. 如果对象内含有一个vptr,现在被重新设定,指向适当的基类的虚函数表(也就是在虚函数中程序员缩写代码之前vptr已经被重新指向为基类了)
  4. 如果有上一层的非虚基类有析构函数,那么会以声明顺序相反的顺序执行
  5. 如果有任何虚基类有析构函数,且当前类是继承链的最底层派生类,那么它们会以原来构造顺序的相反顺序被调用