第5章 构造, 析构, 拷贝语意学

第5章 构造, 析构, 拷贝语意学

C struct的Point声明

/*
    C++称这是一种所谓的Plain Ol' Data声明形式. 如果以C++来编译, 观念上, 编译器会为
    Poit声明一个trivial default construct, 一个trivial destructor, 一个
    trivial copy construct, 以及一个trivial copy assignment operator.
    但实际上, 编译器会分析这个声明, 并为它贴上Plain Ol' Data标签
*/

typedef struct {
    float x, y, z;
    
} Point;

/*
    观念上Point的trivial constructor和destructor都会被产生并被调用, constructor
    在程序起始处被调用 而destructor在程序的exit()处被调用. 然而, 事实上那些
    trivial members要不是没被定义, 就是没被调用, 程序的行为一如他在C中的表现一样

    那么在C和C++中有什么区别?
    (1) 在C中, global被视为一个"临时性的定义", 因为他没有显式的初始化操作. 一个"临时性的定义"
    可以在程序中发生多次. 那些实例会被连接器折叠起来, 只留下单独一个实例, 被放在程序
    data segment中一个"特别留给未初始化的globalobjects使用"的空间. 由于历史的原因,
    这块空间被称为BSS
    (2) 在C++中不支持"临时性的定义", 这是因为class构造行为的隐式应用的缘故. 虽然大家公认这个语言
    可以判断一个class objects或是一个Plain Ol' Data, 但似乎没有必要搞这么复杂.  因此, 
    global在C++中被完全定义(它会阻止第二个或更多的定义). C和C++的一个差异就在于,
    BSS data segment在C++中相对定位不重要. C++的所有全局对象都被以"初始化过的数据来对待"
*/

Point global;

Point foobar() {
    // 既没有构造也没有被析构
    Point local;
    // 没有default constructor实施于new运算符所传回的Point object身上
    Point *heap = new Point;
    // 如果local曾被初始化过, 一切就都没问题, 否则会产生编译警告
    // 观念上, 这样的指定操作会触发trivial copy assignment operator做拷贝搬运操作
    // 然而实际上该object是一个Plain Ol' Data, 所以赋值操作将只是想C那样的纯粹位搬移操作
    *heap = local;
    // 观念上, 会触发trivial destructor, 但实际上destructor要不是没有产生就是没有被调用
    delete heap;
    // 观念上, 会触发trivial copy constructor, 不过实际上return操作只是一个简单的位拷贝操作
    // 因为对象是个Plain Ol' Data
    return local;

}
*/

抽象数据类型

/*
    提供了完整的封装性, 但没有提供任何virtual function
    这个经过封装的Point class, 其大小并没有改变, 还是三个连续的float
*/ 
class Point {
public:
    // 定义了一个构造函数
    Point(float x = 0.0, float y = 0.0, float z = 0.0)
        : _x(x), _y(y),  _z(z) {}
        // 除此之外, 没有定义其他成员函数
private:
    float _x, _y, _z;
};

// 现在有了default construct作用于其上. 由于global被定义在全局范畴中,
// 其初始化操作在程序启动时进行
Point global;

Point foobar() {
    /*
        local的定义会被附上default Point constructor的inline expansion:
        Point local;
        local._x = 0.0, local._y = 0.0, local.z = 0.0;
    */
    Point local;
    /*
        现在则被附加一个"对default Point constructor的有条件调用操作":
        Point *heap = __new(sizeof(Point));
        if (heap != 0)
            heap->Point::Point();
        在条件内才又被编译器进行inline expansion操作
    */
    Point *heap = new Point;
    // 保持着简单的位拷贝操作
    *heap = local;
    // 并不会导致destructor被调用
    delete heap;
    // return时, 同样保持着简单的位拷贝操作, 并没有拷贝操作
    return local;
}

总的来说, 观念上, Point class有一个相关的default copy constructor, copy operator和destructor. 然而它们都是无关痛痒的, 而且编译器实际上更本没有产生它们

包含虚函数的Point声明

包含虚函数时, 除了每一个class object多负担一个vptr之外, virtual function的导入也引发编译器对于Point class产生膨胀作用(如, 编译器会在构造函数总插入初始化vptr的代码)

class Point {
public:
    Point(float x = 0.0, float y = 0.0) : _x(x), _y(y) {}
    virtual float z();
private:
    float _x, _y;
};
  • 自定义构造函数中会安插初始化vptr的代码
  • 因为需要处理vptr, 所以会合成一个copy constructor和一个copy assignment operator, 这两个函数不再是trivial(但隐式的destructor仍然是trivial)
Point *Poit::Point(Point *this, float x, float y) : _x(x), _y(y) {
    // 设定object的virtual table pointer(vptr)
    this->__vptr_Point = __vtbl_Point;
    
    // 扩展member Initialization list
    this->_x = x;
    this->_y = y;

    // 传回this对象
    return this;
}

inline Point *Point::Point(Point *this, const Point &rhs) {
    // 设定object的virtual table pointer(vptr)
    this->__vptr_Point = __vtbl_Point;

    // 将rhs左边中的连续位拷贝到this对象
    // 或是经由member assignment提供一个member

    return this;
}

// 编译器在优化状态下可能会把object的连续内容拷贝到另一个object身上
// 而不会实现一个精确的"以成员为基础的赋值操作"
// 和前一版本相同
Point global;

Point foobar() {
    // 和前一版相同
    Point local;
    // 和前一版相同
    Point *heap = new Point;
    // 这里可能触发copy assignment operator的合成, 以其调用操作的一个
    // inline expansion(行内扩张), 以this取代heap, 而以rhs取代local
    *heap = local;
    // 和前一版相同
    delete heap;
    // 最具戏剧性的改变在这, 下面讨论
    return local;
}

由于copy constructor的出现, foobar很可能被转化为下面:

Point foobar(Point &__result) {
    Point local;
    local.Point::Point(0.0, 0.0);

    // heap的部分与前面相同
    
    // copy constructor的应用
    __result.Point::Point(local);

    // 销毁local对象
    
    return;
}

如果支持named return value(NRV)优化, 会进一步被转化:

Point foobar(Point &__result) {
    __result.Point::Point(0.0, 0.0);

    // heap的部分与前面相同...
    
    return;
}

一般而言, 如果设计之中有许多函数都需要以值方式传回一个local class object, 那么提供一个copy construct就比较合理, 甚至即使default memberwise语意已经足够. 它的出现会触发NRV优化. 然而就像前面例子展现的那样, NRV优化后将不再需要调用copy constructor, 因为运算结果已经被直接计算域"将被传回的object"内了

猜你喜欢

转载自www.cnblogs.com/hesper/p/10609610.html
今日推荐