C++对象模型剖析(十四)一一构造、析构、拷贝语义学(四)

构造、析构、拷贝语义学(四)

对象复制语义学 Object Copy Semantics

当我们设计一个class,并以一个class object指定给另一个class object时,我们有三种选择:

  • 什么都不做,因此得以实施默认行为
  • 提供一个explicit copy assignment operator。
  • 显式拒绝把一个class object指定给另一个 class object。

在这一节,我们需要验证 copy assignment operator的语意,以及它们如何被模塑出来。我们看看本节的例子

class Point {
public:
    Point( float x = 0.0, float y = 0.0, float z = 0.0 );
protected:
    float _x, _y;
};

对于上面的例子来说没什么必要禁止拷贝一个class object。那么编译器默认的行为是否足够呢?编译器默认是使用逐字进行拷贝的,但是上面的class中并没有指针或者应用这一类的东西,所以默认的拷贝行为也是足够的,而且编译器默认的行为不仅足够还有效率。只有在默认行为所导致的语意不安全时或不正确时,我们才需要设计一个copy assignment operator。

如果我们不对Point供应一个copy assignment operator,在以下情况,不会表现出bitwise copy语意:

  • 当class内含一个member object,而其class有一个copy assignment operator时。
  • 当一个class的base class有一个copy assignment operator时。
  • 当一个class声明了任何virtual functions(我们一定不要拷贝右端class object的vptr地址,因为他可能是一个derived class object)时。
  • 当class继承自一个virtual base class(不论此base class有没有copy operator)时。

C++standard上说,copy assignment operators并不表示bitwise copy semantic是nontrivial。实际上,只有nontrivial instance才会被合成出来。

所以,对于Point这个类来说这样的赋值操作:

Point a, b;
a = b;

由bitwise copy完成,把Point b拷贝到Point a,期间并没有copy assignment operator被调用。从语意上或从效率上考虑,这都是我们需要注意的。注意,我们还是可能提供一个copy constructor,为的是把name return value(NRV)优化打开。copy constructor的出现不应该让我们以为也一定要提供一个copy assignment operator。

现在导入一个copy assignment operator,用以说明该在继承之下的行为:

inline Point& Point::operator=(const Point &p)
{
    _x = p._x;
    _y = p._y;
}
// 现在派生一个Point3d class
class Point3d : virtual public Point3d {
public:
    Point3d( float x = 0.0, float y = 0.0, float z = 0.0 );
protected:
    float _z;
}

如果在Point3d中并没有声明一个copy assignment operator,编译器就必须显式合成一个,比如下面这样的

inline Point3d&::operator=(Point3d* const this, const Point3d &p)
{
    // 调用 base class 的函数实例
    this->Point::operator=(p);
    _z = p._z;
    return *this;
}

这里需要注意,由于Point3dPoint的一个虚拟派生类,所以,在同一个继承体系中,不止一个类对Point进行了虚拟继承,这时候问题就出现了,跟我们之前的constructor一样,在调用派生类的时候,可能会调用多次基类的copy assignment operator。但是,copy assignment operator和constructor并不一样,所以这里作者给的一个建议就是:不要任何的virtual base class中声明数据。

析构语意学

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

当class内含member object 或者class的base class,它们中定义了destructor,那么编译器就会自动合成一个或者在我们定义的destructor中进行拓展,调用他们的destructor。一个由程序员定义的destructor被拓展的方式类似constructor被拓展的方式,但顺序相反:

  • destructor的函数本体首先被执行
  • 如果class拥有member class object,而后者拥有destructor,那么它们会以其声明顺序的相反顺序被调用
  • 如果object内含一个vptr,现在被重新设定了指向了适当的base class的virtual table
  • 如果由任何直接的nonvirtual base classes 拥有destructor,它们会以声明顺序的相反顺序被调用
  • 如果有任何virtual base classes 拥有destructor,而目前讨论的这个class 是最尾端(most-derived)的class,那么他们会以原来的构造顺序的相反顺序被调用。

就像constructor一样,目前对于destructor的一种最佳的实现策略就是维护两份destructor实例:

  • 一个complete object实例,总是设定好vptr,并调用virtual base class destructor。
  • 一个base class subobject实例;除非在destructor函数中调用了一个virtual function,否则它绝不会调用virtual base class destructor并设定vptr。

一个object的生命结束于其destructor开始执行之时。由于每一个base class destructor 都轮番被调用,所以derived object实际上变成了一个完整的object。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值