ject的copy constructor:
// 一个被合成出来的copy constructor
// C++伪代码
inline Word::Word(const Word &wd) {
str.String::String(wd.str);
cnt = wd.cnt;
} 有一点值得注意:在这被合成出来的copy constructor中,如整数、指针、数组等等的nonclass members也都会被复制。
不要Bitwise copy Semantics
什么时候一个 class 不展现出bitwise copy semantics呢?有四种情况:
1. 当 class 内含一个member object而后者的 class 声明有一个copy constructor时(不论是被 class 设计者显式声明,或是被编译器合成)
2. 当 class 继承自一个base class 而后者存在有一个copy constructor时
3. 当 class 声明了一个或者多个 virtual functions时
4. 当 class 派生自一个继承串链,其中有一个或者多个 virtual base classe
前两种情况中,编译器必须将members或base class 的copy constructors调用操作插入到被合成的copy constructor中。后两种情况有点复杂,如接下来的小节所述。
重新设定 Virtual Table的指针
编译期间的两个程序扩张操作(只要有一个 class 声明了一个或多个 virtual functions就会如此):
增加一个 virtual function table(vtbl),内含每一个有作用的 virtual function的地址
将一个指向 virtual funtcion table的指针(vptr),插入到每一个 class object中
很显然,如果编译器对于每一个新产生的 class object的vptr不能成功而正确地设好其初值,将导致可怕的后果。因此,当编译器导入一个vptr到 class 中时,该 class 就不再展现bitwise semantics。现在,编译器需要合成出一个copy constructor,以求vptr适当初始化,如下所示:
首先,定义两个classes,ZooAnimal和Bear
class ZooAnimal {
public:
ZooAnimal();
virtual ~ZooAnimal();
virtual void animate();
virtual void draw();
private:
// ZooAnimal的animate()和draw()
// 所需要的数据
};
class Bear : public ZooAnimal {
public:
Bear();
void animate();
void draw();
virtual void dance();
private:
// Bear的animate()和draw()和dance()
// 所需要的数据
};
ZooAnimal class object以另一个ZooAnimal class object作为初值,或Bear class object以另一个Bear class object作为初值,都可以直接靠bitwise copy semantics完成。例如:
Bear yogi;
Bear winnie = yogi;
yogi会被 default Bear constructor初始化,而在constructor中,yogi的vtpr被设定指向Bear class 的 virtual table。因此,把yogi的vptr值拷贝给winnie的vptr是完全的。
当一个base class object以其derived class 内容做初始化操作时,其vptr复制操作也必须保证安全,例如:
ZooAnimal franny = yogi; // 这会发生切割(sliced)
franny的vptr不可以被设定指向Bear class 的virtual table,否则当下面程序片段中的draw()被调用而franny被传进去时,就会炸毁(blow up)
void draw (const ZooAnimal &zoey) {
zoey.draw();
}
void foo() {
// franny的vptr指向ZooAnimal的virtual table
// 而非Bear的virtual table
ZooAniaml franny = yogi;
draw(yogi); //调用Bear::draw()
draw(franny); //调用ZooAnimal::draw()
} 通过franny调用virtual function draw(),调
用的是ZooAnimal实体而非Bear实体(虽然franny是以Bear object yogi作为初始值)。因为franny是一个ZooAnimal object。事实上,yogi中的Bear部分已经在franny初始化时被切割(sliced)。如果franny被声明为一个reference(或者如果它是一个指针,而其值为yogi的地址),那么经由franny所调用的draw()才会是Bear的函数实体。
合成出来的ZooAnimal copy constructor会显式设定object的vptr指向ZooAnimal class 的 virtual table,而不是直接从 class object中将其vptr的值拷贝过来。
处理Virtual Base Class Subobject
Virtual base class 的存在需要特别处理,一个 class object如果以另一个object作为初值,而后者有一个 virtual base class subobject,那么也会使bitwise copy semantics失效。
每一个编辑器对于虚拟继承的支持承诺,都表示必须让derived class object中的virtual base class subobject位置在执行期就准备妥当。维护位置的完整性是编辑器的责任。Bitwise copy semantics可能会破坏这个位置,所以编辑器必须在它自己合成出来的 copy constructor中做出仲裁。例如,在下面的声明中,ZooAnimal成为Raccon的一个virtual base class :
class Raccon : public virtual ZooAnimal {
public:
Raccon(){ /* 设定private data初值 */ }
Racccon(int val) { /* 设定private data初值 */ }
// ...
private:
// 所需要的数据
}; 编译器所产生的代码(用以调用ZooAnimal的default constructor,将Racccon的vptr初始化,并定位出Raccon中的ZooAnimal subject)被插入在两个Raccon constructors之间。
那么memberwise初始化呢?一个 virtual base class 的存在会使bitwise copy semantics无效。其次,问题并不发生于一个class obje