第二章 构造函数语意学(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 = 了,原因很明显,前面才是初始化,这里是赋值。
|
extern void foo(X x);
void bar()
{
X xx;
// 以 对象 xx 作为 foo(X x) 的参数x的初值。会调用 x 的Copy Constructor,这里的 x 是一个临时对象。
foo(xx);
}
|
X foo_bar()
{
X xx;
// ...
return xx;
}
|
class X
{
X( const X& x);
// 也可能是多参形式,第二个参数及其后的参数有默认值
// X( const X& x, int other = 0);
}
|
如果你没有给你的 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 就已足够(但有风险:指针问题)。
|
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.
// 这里还涉及到以子类初始化基类时的切割问题
|
处理 Virtual Base Class Subobject
对于虚拟继承(即:继承自一个虚基类),编译器必须维护 派生类中虚基类的位置(要理解这个“位置”的含义,首先你的了解继承后的对象模型是怎样的)。
直接按位拷贝可能会破坏这个"位置"。
class Raccoon : public virtual ZooAnimal
{
// ...
}
class RedPanda : public Raccoon
{
// ...
}
|
【说明】近期项目上线,工作较忙,更新像蜗牛!后续加快节奏!