在三种情况下,会以一个对象的内容作为另一个对象的初值:
- 定义一个对象并显示初始化
- 对象作为参数传递给函数
- 对象作为函数的返回值
和默认构造函数一样,当用户未显式定义复制构造函数时,编译器只有在某些条件下才会合成一个nontrivial的复制构造函数。下面主要讨论在哪些情况下,编译器才会自动合成一个复制构造函数。
当一个class没有声明一个复制构造函数,那么:
- 当这个class不展现bitwise copy语义时,编译器会合成一个复制构造函数
- 当这个class展现出bitwise copy语义时,编译器不会合成复制构造函数
那么什么是bitwise copy语义呢?看下面一个例子。
如果有一个如下所示的类:
class Foo {
public:
int x, y;
};
那么编译器不会自动生成一个复制构造函数,因为对于这些内置类型,已经能够实现逐位拷贝了。但如果是下面这样的类:
class Foo {
public:
int x, y;
string str;
};
在这种情况下,编译器必须合成一个复制构造函数,函数内部调用str对象的复制构造函数,其它内置类型依旧逐位拷贝。
从上面两个例子可以得出结论:class中只存在整型、指针、数组等内置类型,则展现出bitwise copy语义。如果class中存在类类型,则不展现出bitwise copy语义。
一个class在以下4种情况不展现出bitwise copy语义:
1、类中有一个对象成员,并且该对象定义(显式定义或编译器合成)了复制构造函数。
2、类继承自一个基类,而基类存在一个(显式定义或编译器合成)复制构造函数。
上面两种情况比较好理解。对象成员或基类中有复制构造函数,所以编译器需要插入一些代码调用这些复制构造函数,这些代码被安插在合成的复制构造函数中。
注意,若已经显示定义了复制构造函数,则编译器不会执行插入操作。
测试代码如下:
#include <iostream>
using namespace std;
class Foo {
public:
Foo() {} // 此构造函数必须定义
Foo(const Foo &f) { cout << "Foo's copy construct!" << endl; }
};
class Bar: public Foo {
public:
// 未定义复制构造函数
int x, y;
Foo foo;
};
int main()
{
Bar ba;
Bar bb = ba;
return 0;
}
上述代码同时满足情况1、2,所以编译器会在合成的复制构造函数中调用了两次Foo类的复制构造函数。
3、当类中声明了虚函数
一说到虚函数,就会联想到virtual function table(vtbl)和vptr。在对象间进行复制时,对vptr的复制操作非常重要。虽说vptr是一个指针,可以按照逐位拷贝原则进行复制,但有时会发生很严重的错误:vptr都指向了同一个virtual function table。所以当编译器导入一个vptr后,为了正确初始化vptr,编译器需要合成复制构造函数进行相关操作。
具体来说,当有如下继承体系时:
当同类对象间进行复制初始化时,采用的是逐位拷贝,vptr指向相同的虚函数表。如下图所示:
这样的拷贝是安全的。
当用派生类初始化基类时,不能采用逐位拷贝,不同类的vptr指向各自的虚函数表,如下图所示:
不同类对象中的vptr指向了各自的vtbl,这是由合成出来的复制构造函数所要完成的任务。正因为如此,发生切割后无法实现多态性质。
测试代码:
#include <iostream>
using namespace std;
class Foo {
public:
virtual void func()
{ cout << "virtual function in Foo!" << endl; }
};
class Bar: public Foo {
public:
void func()
{ cout << "virtual function in Bar!" << endl; }
};
int main()
{
Bar b1;
Bar b2 = b1; // vptr直接复制,指向相同的virtual function table
b2.func();
Foo foo = b1; // 发生切割,vptr不直接复制,指向不同的virtual function table
foo.func();
return 0;
}
结果正如上面所说。
4、有虚拟基类的情况
一个对象以另一个同类的对象作为初值时,bitwise copy是有效的。
一个对象以其派生类的某个对象作为初值时,bitwise copy失效。
可以看到,编译器对复制构造函数的隐式操作和默认构造函数是相似的。编译器在需要某些特别的初始化操作时,才会合成复制构造函数。否则,对象之间直接进行逐位拷贝,编译器不会合成复制构造函数。
环境:
Win7 + VS2013
参考:
《深度探索C++对象模型》 P48-P60.
|