【深度探索C++对象模型】第二章 构造函数语意学(中)

来源:互联网 发布:单片机12864程序解析 编辑:程序博客网 时间:2024/05/17 08:55
第二章 构造函数语意学(The Semantics of Constructors)(中)
—— 本书作者:Stanley B.Lippman
(接上篇)
三、Copy Constructor 的建构操作
    学习目标:
  • 什么是 拷贝构造函数(Copy Constructor) ?
  • 何时会调用 拷贝构造函数(Copy Constructor)? 
  • 编译器什么时候会为我们合成一个“有用”的拷贝构造函数?
    何时会调用拷贝构造函数(Copy Constructor)?
    当一个 对象 通过另一个对象来初始化时,前者的 拷贝构造函数 会被调用。
    1. 第一种情况: 初始化。
class X
{
// ...
};
X x;    // 定义一个 X 对象
X xx = x;    // xx 用 x 来初始化,但如果是: X xx; xx = x; 这里调用的就是 operater = 了,原因很明显,前面才是初始化,这里是赋值。
    2. 第二种情况:对象 作为函数参数。
extern void foo(X x);

void bar()
{
    X xx;
    // 以 对象 xx 作为 foo(X x) 的参数x的初值。会调用 x 的Copy Constructor,这里的 x 是一个临时对象。
    foo(xx);
}
    3. 第三种情况:对象 作为函数返回值。
X foo_bar()
{
    X xx;
    // ...
    return xx;
}
    拷贝构造函数的表现形式:
class X
{
    X( const X& x);
    // 也可能是多参形式,第二个参数及其后的参数有默认值
    // X( const X& x, int other = 0);
}
    Default Memberwise Initialization
    如果你没有给你的 class 显示的定义 拷贝构造函数,那么在发生需要调用拷贝构造函数的情形时,内部是通过 default Memberwise Initialization 手法完成的。
    Default Memberwise Initialization 看起来像什么样子呢?
class A
{
public:
    A(const A& a);
private:
    int a;
    int b;
}
// 编译器合成的 Copy Constructor 可能像这样:
A::A(const A& a)
{
    this->a = a.a;
    this->b = a.b;
}
    【注】当你的类里有指针成员的时候,要格外小心。具体什么问题,可参考 Effective C++ 里的条例。
    当我们没有显示定义 拷贝构造函数 时,编译器什么时候才会为我们合成一个?
    当一个 class 没有显示定义 copy constructor 的时候,编译器会在必要的时候为这个class合成出来。所谓的必要时,是指 class 没有展现出 Bitwise Copy Semantics(位逐次拷贝) 时。而当 class 展现出 位逐次拷贝 时,并不需要合成一个 Copy Constructor,默认的 Memberwise Initialization 就已足够(但有风险:指针问题)。
    什么时候一个 class 不展现出 Bitwise Copy Semantics 呢?有四种情况:
    1. 当 class 内含一个 成员对象,且该成员对象的类声明有一个 copy constructor 时(不一定是明确声明的,也可能是由编译器合成的 copy constructor)。如,你的 class 里有一个 std::string 成员时(std::string 的copy constructor 不是合成的,是明确声明的),这时如果你没有明确声明一个 copy constructor 编译器会为你合成一个。
    2. 当 class 继承自一个 base class ,而后者存在一个 copy constructor 时(合成的或者声明的都算)。
    3. 当 class 声明了一个或多个 virtual functions 时。
    4. 当 class 的继承串链中,其中有一个或者多个 virtual base classes 时。
    重新设定 Virtual Table 的指针__vptr
    前面曾学到过,C++对象模型是如何支持 virtual 机制的。是通过在 class 内安插了一个 vptr 指向其相应的 vtbl。因此,如果编译器不能正确的初始化 vptr,将导致严重的后果。所以,当一个 class 有 virtual functions 时,就不在展现出 Bitwise semantics。于是编译器将会合成一个 copy constructor 来初始化 vptr。下面是列子。
    【注】一个 class 对应一个 vtbl。相同 class 的 objects 的 vtbl 相同,也即 vptr 指向的地址相同。
class Base
{
public:
    Base();
    virtual ~Base(); // 基类的析构函数一定要 virtual,否则会导致严重后果。
    
    virtual void draw();
};

class Derive : public Base
{
public:
    Derive();
    void draw(); // 虽没写明 virtual,但实际上是 virtual。
};

Derive yogi;
Derive winnie = yogi; // OK! winnie 和 yogi 的 vtbl 地址相同。可以直接拷贝 vptr 的值。
Base franny = yogi; // 注意,franny 的 vptr 和 yogi 的 vptr 指向不同的 vtbl,因此,copy contructor 需要重新设定 franny 的值,防止其指向 Derive 的vtbl.
// 这里还涉及到以子类初始化基类时的切割问题
    也就是说:合成出来的 Base class 的拷贝构造函数会明确设定其对象的 vptr 指向 Base class 的 vtbl,而不是直接从右手边的 class object 中的 vptr 地址拷贝过来。
    处理 Virtual Base Class Subobject
    对于虚拟继承(即:继承自一个虚基类),编译器必须维护 派生类中虚基类的位置(要理解这个“位置”的含义,首先你的了解继承后的对象模型是怎样的)。
    直接按位拷贝可能会破坏这个"位置"。
class Raccoon : public virtual ZooAnimal
{
// ...
}
class RedPanda : public Raccoon
{
// ...
}
    当我们以一个Raccoon对象作为另一个Raccoon对象的初值时,不会有任何问题,默认的Bitwise semantics就足以搞定。但当我们以Derived class:RedPanda作为Raccoon的初值时,就需要编译器合成出一个 拷贝构造函数,以正确的通过 RedPanda(子类) 初始化 Raccoon(父类) 。

【说明】近期项目上线,工作较忙,更新像蜗牛!后续加快节奏!

0 0