【C++】深度探索C++对象模型之构造、析构、拷贝语意学

一、纯虚函数的存在

可以定义和调用一个pure virtual function, 不过只能被静态调用(invoked statitcally),不能经由虚拟机制调用

Abstract_base::interface();
如上述,Abstract_base是一个虚基类。这只取决于类设计者要不要这么做。

但是对于pure virtual destrcutor, 类设计者一定要定义它,因为每个drived class destructor会被编译器加以扩张,然后以静态方式进行调用"每一个virtual base class”以及“上一层base class”的destructor。

所以一般比较好的处理方式,不要把virtual destructor声明为pure。


二、“无继承”情况下的对象构造

有三种对象产生的方式:global内存控制,local内存配置和heap内存配置。


两种初始化方式

Point local1 = {1.0, 1.0, 1.0};

Point local2;
local2._x = 1.0;
local2._y = 1.0;
local2._z = 1.0;
local1对应是explicit initialization list,这比local2声明有效的多。因为当函数的activation record放进程序堆栈时,上述的initialization list中的常量就被放进local内存中了。  并且,explicit initialization list只有当类数据成员声明为public时才有效。


三、为继承做准备

当一个类声明多了virtual function,也就是可以进行操作的动态决议时,会有如下变化:

  • 每个class object多负担一个vptr
  • 所定义的contructor将发生膨胀,被附加一些代码以便将vptr进行初始化
  • 合成一个copy constructor和copy assignment operator,主要是由于vptr的存在,bitwise copy可能会发生问题。
Point foobar()
{
    Point local;
    // ...
    return local;
}
会被转化为如下:
Point foobar( Point &_result )
{
    Point local;
    // ...stuff...
    _result.Point::Point(local); //调用copy constructor
    local.Point::~Point();
    return;
}
如果设计的类中,函数需要以传值方式(by value)传回一个local class object,最好提供一个copy construtor。

四、继承体系下的对象构造

T object;
编译器会扩充每一个constructor,扩充程度视class T的继承体系而定。大致扩充如下:
  • 记录在member initialization list中的data members初始化操作会被放进constructor的函数本体,并以members的声明顺序为顺序。
  • 如果member没有出现在initialization list中,但有一个default constructor,那么default constructor必须背调用。
  • 如果class object有vptr,则必须设定初值指向适当的virtual table。
  • 所有的上层base class constructors必须被调用,以base class的声明顺序为顺序。
  •                  如果base class被列于member initialization list中,那么任何显示指定的参数都需要被传递过去
  •                  如果没有列于上述,则调用defualt constructor或default memberwise copy constructor。
  •                  如果base class是多重继承下的第二或后继的base class,那么this指针需要有所调整
  • 所有的virtual base class constructor必须被调用,从左至右,从深到浅。
  •                  1. 如4(1)
  •                  class的每一个virtual base class subobject的偏移位置必须在执行期可被存取。
  •                  支持最底层的constructor的某种调用机制需要放进来。

如果Point destructor是inline函数,每一个调用操作在调用地点会被扩展开来。

Line b = a;
被写出来时,implicit Line copy constructor会被合成出来,成为一个inline public member
a = b;
被写出来时,implicit copy assignment  operator 会被合成出来,成为一个inline public member。

五、虚拟继承

对于虚拟继承,越是往后的derive class,其相应就需要完成对共享基类对象的构造。这是顶端派生类所干的事。同时,在扩展中还需要将标记,使得基类对象只被实例化一份。而后继的类不需要再实例化共享基类。
只有当一个完整的class object被定义出来,virtual base class constructor才能被调用,如果object只是某个完整的object的suboject,它就不会被调用。

一般而言,一个class的constructor中,经由构造中的对象来调用一个virtual function,其函数实例化应该是此class中有作用的那一个。
constructor的调用顺序,从根源到末端, 由内向外,也就是从基类到派生类方向。

在构造过程中,对于每次调用,该如何决议 ==> 需要对每个调用操作以静态方式决议,不要用到虚拟机制。也就是类似
Point3d::size();
同时,如果size中需要调用其他函数实例,这个时候急需要经由virtual机制决定其动态。

那到底什么时候是以静态方式调用,什么时候以虚拟机制调用?
根本解决之道,在执行一个constructor时,必须限制一组virtual function候选名单。换一句话说,就是为了控制一个class中的有所作用的函数,编译系统需要控制住vptr的初始化和设定操作。

vptr的初始化操作应该如何处理?
每一个constructor都一直等待到其base class constructor 执行完毕之后才设定其对象的vptr,那么每次就能够调用到正确的virtual function实例。

constructor的执行算法通常如下:
  1. 在derived class constructor中,所有的“virtual base classes”及“上一层base class”的constructors会被调用。
  2. 上述完成之后,对象的vptr(s)被初始化,指向相关的virtual table(s);
  3. 如果有member initialization list的话,将在constructor体内扩展出来。这必须在vptr设定之后才这么做,以确保有一个virtual member function被调用。
  4. 执行程序猿的代码。

六、对象复制语意学

只有在默认行为所导致的语意不安全或不正确时,我们才需要设计一个copy assignment operator。
如果不对类提供拷贝复制符,那么编译就和copy constructor的情形一样,不会产生实例。

什么时候不会表现出bitwise copy?
  1. 当一个class内含一个member object,而其class有一个copy assignment operator
  2. 当一个class的base class有一个copy assignment operator
  3. 当一个class声明了任何virtual function(因为不能拷贝右端的vptr)
  4. 当一个class继承自一个virtual base class。
copy assignment 缺乏member assignment list,也就是如下性质是不支持的:
inline Point3d&
Point3d::operator=( const Point3d &p3d ) : Point( p3d ), z ( p3d._z ) {}

一种方法可以保证most derived class完成virtual base class subobject的copy行为,也就是在derived class 的 copy assignment operator函数实例最后,显示调用operator。如:
inline Vertex3d &
Vertex3d::operator=( const Vertex3d &v )
{
    this->Point3d::operator=(v);
    this->Vertex::operator=(v);
    this->Point::operator=(v);
}

七、析构语意学

如果class没有定义destructor,那么只有在class内含的member object(抑或是class自己的base class)拥有destructor的情况下, 编译器才会自动合成一个出来。否在destructor被视为不需要。也就不需要合成。

一个destructor被扩展的方式类似constructor,但顺序相反。
  • 如果object内含一个vptr,那么首先重设相关的virtual table 3
  • destructor的函数本体现在被执行,也就是说vptr会在程序员的代码执行前被重设 1
  • 如果class拥有member class objects,而后者拥有destructor,那么它们会以其声明的相反顺序被调用 2
  • 如果有任何直接的nonvirtual base classes 拥有 destructor,它们会以其声明的顺序的相反顺序被调用 4
  • 如果有任何virtual base classes拥有destructor,而目前这讨论的这个class是最尾端(most derived)的class,那么她们会以其相反的构造顺序的相反顺序被调用。 5


参考:

侯捷 深度探索C++对象模型



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值