关于复制构造函数的简单介绍,能够看我之前写过的一篇文章C++复制控制之复制构造函数该文章中介绍了复制构造函数的定义、调用时机、也对编译器合成的复制构造函数行为作了简单说明。本文因须要会涉及到上文的一些知识点,但仍是推荐先阅读上文。html
本文主要从编译器角度对复制构造函数进行分析,纠正之前对复制构造函数的一些错误认识。前端
咱们首先来看复制构造函数涉及的两个概念:浅拷贝与深拷贝。假设有两个对象:A与B,它们是同类型的,下面分析B=A时浅拷贝与深拷贝行为。git
浅拷贝简单地把B复制为A的引用或指针,能够认为B复制了A的地址,复制的结果是B与A拥有相同的地址,它们将指向相同的内存区域的相同的数据。在这种状况下,若是对象A被销毁,那么对对象B的某些操做将是非法的。github
深拷贝时使用一个对象的内容来建立同一个类的另外一个实例,B复制了A的全部成员,并在内存中不一样于A的区域为B分配了存储空间,也便是说B拥有本身的资源。在这种方式下,若是A被销毁时,B依旧有效,由于A与B并无共享存储空间,重载复制操做符时要采用这种深拷贝方式。安全
当你明确知道你中程序中使用的是浅拷贝而且明白它带来的后果时你才去使用浅拷贝。而当你有大量的指针要处理时,对指针作浅拷贝是一个糟糕的作法。若是咱们类的数据成员都是内置类型而没有指针,那么简单的浅拷贝是能够接受的,反之若是类中有须要深层复制的内容,则咱们的复制构造函数必须以深拷贝的方式进行对象的复制。函数
逐个成员:咱们把merberwise copy当成deep copy来理解就好了,这种复制会根据每一个成员的类型来进行复制,对于指针类型会复制指针所指的值(从新分配存储区域)。设计
Bitwise copy 字面上的意思是逐位拷贝。举个例子,对于两个同类型的对象A与B,对象A在内存中占据存储区为0x0-0x9,执行B=A时,使用Bitwise copy拷贝语义,那么将会拷贝0x0到0x9的数据到B的内存地址,也就是说Bitwise是字节到字节的拷贝。这样子理解起来,实际上Bitwise copy = shallow copy。指针
《Effective C++》中说到:code
若是你本身没声明,编译器就会为它声明一个copy构造函数、一个copy assignment操做符和一个析构函数。htm
实际上在《深度探索C++对象模型》中对编译器的行为并非这样描述的。对于默认构造函数与复制构造函数,都须要类知足必定的条件时编译器才会帮你合成。那么须要知足些什么条件呢?这条件就是:类不展示bitwise copy 语意的时候。
当咱们的类中只含有内置类型或复合类型时,类展示了Bitwise copy 语意。这种状况下并不须要合成一个默认复制构造函数,也即编译器不会帮咱们合成复制构造函数。如:
//如下声明展示了bitwise copy 语意
class Word
{
public:
Word(chartemp){ str = temp; };
//...
int cnt;
char str;
};
int main()
{
char * temp = "hello";
Word A(temp);
Word B(A);
cout << B.str;
system("pause");
return 0;
}
运行程序,你会神奇地发现程序竟然通得过编译,并且B也获得了正确的赋值,就好像类中有了一个复制构造函数同样。不是说编译器在Bitwise copy语意下不会进行复制构造函数的合成吗?
说实话这问题我也很疑惑,查看了许多资料,反复看了《深度探索C++对象模型》后,我最终这样认为:展示了Bitwise copy语意的类编译器不会为它写一个函数实体进行成员的复制。展示Bitwise copy语意的类,类的数据成员按照Memberwise Initialization(注意不一样于Memberwise copy)进行初始化,具体是这样的:当类对象以同类型的另外一个对象进行初始化时,把每个内建的或派生的date member(例如一个指针或一数目组)的值,从一个对象拷贝到另外一个对象,不过它并不会拷贝其中的member class object,而是以递归的方式施行以上的拷贝。实施这些步骤并不在函数实体内。
当类不展示出Bitwise copy语意且类设计者没有为类定义一个复制构造函数,这时编译器就会为合成一个复制构造函数实体。那么在什么状况下一个类才会不展示出Bitwise copy 语意呢?
前两种状况中,编译器必须将“类成员或基类的复制构造函数调用操做”安插到新合成的复制构造函数中去,若是类设计者已经明确声明了一个复制构造函数,则这些调用操做代码将插入到已有的复制构造函数中去(在函数体的最前端插入)。
后两种操做涉及到了虚表指针与虚基类指针的产生于初值设置。咱们知道,当一个类含有虚函数时(不管这虚函数是类自己定义仍是继承而来),在编译期间会有如下两个程序扩张操做:
显然,若是编译器对每一个新定义的类对象不能正确地设置好初值,将致使严重的后果。因此编译器须要合成出一个复制构造函数来适当地初始化类对象的vptr。万一类设计者明肯定义了本身的复制构造函数,则编译器会把设置vptr的操做插入到已有的复制构造函数中。而vptr的复制又有两种状况:
同类类型的对象各自的vptr老是指向了同一个位置:该类的虚表指针。这时两个对象的vptr的复制均可以直接考”bitwise copy“来完成(除了可能会有的其余指针成员)。因此同类型对象间的vptr复制老是安全的。
-把子类对象vptr复制给父类对象
不用担忧把子类对象复制给父类对象时,vptr也会采用bitwise copy来复制,这点编译器给咱们作了保证:编译器合成的默认构造函数(或者说在明确声明的复制构造函数中安插的代码)会明确设定父类的vptr指向父类的虚函数表,而不是采用傻瓜式直接复制子类对象vptr。
而对于第4点涉及到虚基类的状况,能够看C++合成默认构造函数的真相中有关虚基类的描述。虚基类的存在须要特殊处理,一个类对象若是以另外一个对象做为初值,然后者派生于虚基类,那么这种状况下bitwise copy语意也会失效,编译器会对派生自虚基类的类合成一个默认构造函数,在其中安插一些操做。对于虚继承,编译器有承偌:派生类对象中的虚基类位置在执行期就要准备稳当,维护”位置的完整性“是编译器的责任,而显然的,Bitwise copy 语意会破坏这个位置(这种傻瓜式的复制好像只适用内置类型的复制以及同类型对象间vptr的复制),因此编译器必须在它本身合成出来的复制构造函数中作出仲裁。一样的,若是类设计者明确声明了复制构造函数,则这些冲裁代码将安插在这个复制构造函数中。
在类不知足"Bitwise copy"语意时编译器会采起行动,若是类设计者没有明肯定义复制构造函数,则编译器将行动实施于合成构造函数中,不然将这些行动实施于已有的复制构造函数中。值得注意的是,编译器除了对vptr与虚基类的处理能保证安全以外,对于内置类型或复合类型如指针的复制都是采用浅拷贝,因此,当咱们的类中含有指针的时候,咱们须要本身写一个复制构造函数来对对象的指针进行深拷贝,而vptr与虚基类的问题,就交给编译器吧!