深度探索C++对象模型<四>

#copy constructor 的建构操作

在以下的三种情况下,会以一个对象的内容去初始化另外一个对象:

  • 明确的初始化操作,如 X xx = x;
  • 对象被当做函数参数传入函数中;
  • 函数返回一个对象时;

形式:

1
2
3
X::X(const X& x);
//可以是多参数模式,但第二个参数及其之后的参数都要以默认值提供
Y::Y(const Y& y, int x = 0);

Default Memberwise Initialization

如果一个类没有显式的定义拷贝构造函数,那么在发送以其它对象进行初始化就会进行所谓的default memberwise initialization行为。也就是把内部的数据成员的值一个一个地拷贝都被初始化的类对象中,如果其中有其他类的对象成员,那么就会递归地对该类进行default memberwise initialization

而所谓的memberwise initialization分为两种:Bitwise Copy and Copy Constructor。

Bitwise Copy Semantics

在C++中,默认进行的就是Bitwise Copy。例如这种情况只包含原生的数据成员,就是进行Bitwise Copy:

1
2
3
4
5
6
7
8
Class Word {
public:
Word(const char* );
~Word(){delete []str;}
private:
int cnt;
char *str;
};

但这种情况下,会出现的问题就是:由于只是单纯地拷贝指针的值,在进行拷贝之后,会出现两个对象的内部的指针指向了同一块内存。如果某个对象被销毁了,那么指针所指向的内存也回收了。这样另外一个对象的指针就变成了野指针。

不要Bitwise Copy Semantics

在以下的情况下,一个类不表现出Bitwise Copy Semantics:

  • 当class内含一个member object,而后者的class声明有一个copy constructor时(不论是用户自己定义的,还是编译器生成的)
  • 当class继承自一个base class而后者存在一个copy constructor时(再次强调,不论是显示声明或编译器合成)
  • 当class声明了一个或多个virtual functions时
  • 当class派生自一个继承串链时,其中有一个或多个virtual base classes时

前两种操作,必须把成员对象或者基类对象的拷贝构造代码插入到合成的拷贝构造函数中;

至于后面两种情况

重新设定virtual table的指针

由于如果类中有虚函数,那么需要为该类增加一个虚表,并且为该类的每个对象增加一个指针,指向虚表。

这就是为什么编译器需要合成一个拷贝构造函数,主要目的就是为了为新的对象增加一个指针。

这样可以避免了这种情况,子啊这种情况中,base内含的指针应该是指向基类的虚表,如果是Bitwise Copy,那就变成派生类的虚表了

1
Base base = derived;

virtual base class subject

这种情况跟上面的差不多,也是为了保证virtual base class subobject的位置在编译器被确定下来,而合成拷贝构造函数。这种情况一般也是发送在用派生类对象去初始化基类,因为这样如果没有合成拷贝构造函数,将会难以确定virtual base class subobject的位置。