有三种情况,会以一个object的内容作为另一个class object的初值。最明显的一种情况当然就是对一个object做显示的初始化操作,另两种情况是当object被当做参数交给某个函数时,以及当函数传回一个class object时。
如果class没有提供一个explicit copy constructor又当如何?
当class object以“相同class的另一个object”作为初值,其内部是以所谓的default memberwise initialization手法完成的,也就是把一个内建的或派生的data member(例如一个指针或一个数组)的值,从某个object拷贝一份到另一个object身上。不过它并不会拷贝其中的member class object,而是以递归的方式施行memberwise initialization。
例如:
class String {
public:
//没有explicit copy constructor
private:
char *str;
int len;
};
一个String object的default memberwise initialization发生在这种情况之下:
String noun("book");
String verb = noun;
其完成方式就好像个别设定每一个members一样:
//语义相等
verb.str = noun.str;
verb.len = noun.len;
如果一个String object被声明为另一个class的member,像这样:
class Word {
public:
//没有explicit copy constructor
private:
int _occurs;
String _word;//String object成为class Word的一个member
};
那么一个Word object的default memberwise initialization会拷贝其内建的member _occurs,然后再于String member object _word 身上递归实施memberwise initialization。
这样的操作实际上如何完成?ARM告诉我们:概念上而言,对于一个class X,这个操作是被一个copy constructor实现出来的……其中主要的字眼是“概念上”。这个注释又紧跟着一些解释:一个良好的编译器可以为大部分class objects产生bitwise copies,因为它们有bitwise copy semantics……也就是说,“如果一个class未定义出copy constructor,编译器就自动为它产出一个”这句话不对,而是应该像ARM所说:default constructor和copy constructor在必要的时候才由编译器产生出来。“必要”意指当class不展现bitwise copy semantics时。
就像default constructor一样,C++ standard说,如果class没有声明一个copy constructor,就会有隐式的声明或隐式的定义出现。和以前一样,C++ standard把copy constructor区分为trivial和nontrivial两种。只有nontrivial的实例才会被编译器合成于程序中。决定一个copy constructor是否为trivial的标准在于class是否展现出所谓的“bitwise copy semantics”。
举个例子:
//以下声明展现了bitwise copy semantics
class Word {
public:
Word(const char*);
~Word() {
delete[] str;
}
//没有explicit copy constructor
private:
int cnt;
char *str;
};
这种情况下并不需要合成出一个default copy constructor,因为上述声明展现了“default copy semantics”。(当然程序的执行会有问题。local object verb和global object noun都指向相同的字符串。在退出foo()之前,local object verb会执行destructor,于是字符串被删除,global object noun从此指向一堆无意义之物。member str的问题只能够靠“由class设计者实现出一个explicit copy constructor以改写default memberwise initialization”或是靠“不允许完全拷贝”来解决。)
然而如果class Word声明这样:
//以下声明并未展现出bitwise copy semantics
class Word {
public:
Word(const String&);
~Word();
private:
int cnt;
String str;//class Word内含一个member object str,而class String声明有一个copy constructor
};
其中String声明了一个explicit copy constructor:
class String {
public:
String(const char*);
String(const String&);
~String();
};
这种情况下,编译器必须合成出一个copy constructor,以便调用member class String object的copy constructor:
//一个被编译器合成出来的copy constructor
//C++伪代码
inline Word::Word(const Word& wd)
{
str.String::String(wd.str);
cnt = wd.cnt;
}
有一点很值得
注意:在这被合成出来的copy constructor中,如整数、指针、数组等等的nonclass members也都会被复制,正如我们所期待的一样。
什么时候一个class不展现出“bitwise copy semantics”呢?有4种情况:
1.当class内含一个member object而后者的class声明有一个copy constructor时(不论是被class设计者显示地声明,就像前面的String那样:或是被编译器合成,像class Word那样)。
2.当class继承自一个base class而后者存在一个copy constructor时(再次强调,不论是被显示声明或是被合成而得)。
3.当class声明了一个或多个virtual functions时。
4.当class派生自一个继承串链,其中有一个或多个virtual base classes时。
前两种情况中,编译器必须将member或base class的“copy constructor调用操作”安插到被合成的copy constructor中。
情况3和4有点复杂,接下来我们讨论。
回忆编译期间的两个程序扩张操作(只要有一个class声明了一个或多个virtual functions就会如此):
1.增加一个vtbl,内含每一个有作用的virtual function的地址。
2.一个指向virtual function table的指针(vptr),安插在每一个class object内。
很显然,如果编译器对于每一个新产生的class object的vptr不能成功而正确的设好其初值,将导致可怕的结果。因此,当编译器导入一个vptr到class之中时,该class就真不再展现bitwise semantics了。编译器就需要合成出一个copy constructor以求将vptr适当地初始化。
以一个相同class的object B作为object A的初值,都可以直接靠“bitwise copy semantics”完成(除可能会有的pointer或reference member,需要程序员显示定义否则带来一些问题)。
当一个base class object以其derived class的object内容做初始化操作时,其vptr赋值操作也必须保证安全。
base class object的vptr不可以被设定指向derived class的virtual table,如果vptr被直接“bitwise copy”的话,就会导致问题(发生过切割行为)。所以,合成出来的base class copy constructor会显示设定object的vptr指向base class的virtual table,而不是直接从右手边的derived class object中将其vptr现值拷贝过来。除非左边的base class object是一个pointer或reference。
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中做出仲裁。
总结:我们看过4种情况,在那些情况下class不再保持“bitwise copy semantics”,而且default copy constructor如果未被声明的话,会被视为nontrivial。在这4种情况下,如果缺乏一个已声明的copy constructor,编译器为了正确处理“以一个class object作为另一个class object的初值”,必须合成出一个copy constructor。