[读书笔记] - 《深度探索C++对象模型》第5章 构造、解构、拷贝语意学

1.纯虚函数

纯虚函数可以被定义和调用,不过它只能被静态地调用,不能经由虚拟机制调用。

// 定义pure virtual function
inline void Abstract_base::interface() const
{

}

inline void Concrete_derived::interface() const
{
    // ok: 静态调用
    Abstract_base::interface();
}

要不要定义纯虚函数,全由class设计者决定。但是pure virtual destructor是个例外,class设计者一定得定义它。因为每一个derived class destructor会被编译器加以扩展,以静态调用的方式调用其“每一个virtual base class”以及“上一层base class”的destructor。因此,只要缺乏任何一个base class destructor的定义,就会导致链接失败。

一个比较好的替代方案是,不要把virtual destructor声明为pure。

2.虚拟继承

class Point3d : virtual public Point { ... };
class Vertex : virtual public Point { ... };
class Vertex3d : public Point3d, public Vertex { ... };
class PVertex : public Vertex3d { ... };

当Point3d和Vertex同为Vertex3d的subobjects时,它们对Point constructor的调用操作一定不可以发生,取而代之的是,作为一个最底层的class,Vertex3d有责任将Point初始化。而更往后(往下)的继承,则由PVertex(不再是Vertex3d)来负责完成“被共享的Point subobject”的构造。

3.vptr初始化语意学

当我们定义一个PVertex object时,constructors的调用顺序是:

Point();
Point3d();
Vertex();
Vertex3d();
PVertex();

假设这个继承体系中的每一个class都定义了一个virtual function size(),且每一个constructors内带一个调用操作,像这样:

Point3d::Point3d(float x, float y, float z)
    : _x(x), _y(y), _z(z)
{
    std::cout << "size: " << size() << std::endl;
}

当我们定义PVertex object时,前述的五个constructors中的每一次size()调用会被决议为“当前正在执行的constructor所对应的class”的size()函数实体。即,在Point3d constructor中调用的size()函数,必须被决议为Point3d::size()而不是PVertex::size()。更一般地,在一个class(本例为Point3d)的constructor(和destructor)中,经由构造中的对象(本例为PVertex对象)来调用一个virtual function,其函数实体应该是在此class(本例为Point3d)中有作用的那个。

如果size()之中又调用一个virtual function,会发生什么事情呢?这种情况下,这个调用也必须决议为Point3d的函数实体。而在其他情况下,这个调用时纯正的virtual,必须经由虚拟机制来决定其归向。也就是说,虚拟机制本身必须知道是否这个调用源自于一个constructor之中。

4.对象复制语意学

copy assignment operator在虚拟继承情况下行为不佳,需要小心地设计和说明。许多编译器甚至并不尝试取得正确的语意,它们在每一个中间的copy assignment operator中调用每一个base class instance,于是造成virtual base class copy assignment operator的多个实体被调用。

有一种方法可以保证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);
    // place this last
    this->Point::operator=(v);
}

这并不能够省略subobjects的多重拷贝,但却可以保证语意正确。

建议尽可能不要允许一个virtual base class的拷贝操作。甚至不要在任何virtual base class中声明数据。

5.解构语意学

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

一个由程序员定义的destructor被扩展后执行的顺序:

1>destructor的函数本身首先被执行;

2>如果class拥有member class objects,而后者拥有destructors,那么它们会以其声明顺序的相反顺序被调用;

3>如果object内带一个vptr,则现在被重新设定,指向适当的base class的virtual table;

4>如果有任何直接的(上一层)nonvirtual base classes拥有destructor,它们会以其声明顺序的相反顺序被调用;

5>如果有任何virtual base classes拥有destructor,而当前讨论的这个class是最尾端(most-derived)的class,那么它们会以其原来的构造顺序的相反顺序被调用。

一个object的声明结束于其destructor开始执行之时。由于每一个base class destructor都轮番被调用,所以derived object实际上变成了一个完整的object。例如一个PVertex对象归还其内存空间之前,会依次变成一个Vertex3d对象、一个Vertex对象、一个Point3d对象,最后成为一个Point对象。当我们在destructor中调用member functions时,对象的蜕变会因为vptr的重新设定而受到影响。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值