本文主要介绍《深度探索 C++对象模型》之《构造函数语义学》中的 Copy Constructorios
首先须要明确,构造函数什么时候会被调用呢?cppreference 中已经有了足够详细地说明:c++
凡在对象从同类型的另外一对象(以直接初始化或复制初始化)初始化时,调用复制构造函数(除非重载决议选择了更好的匹配或其调用被消除),状况包括:
初始化:T a = b; 或 T a(b);,其中 b 类型为 T;
函数实参传递:f(a);,其中 a 类型为 T 而 f 为 Ret f(T t);
函数返回:在如 T f() 这样的函数内部的 return a;,其中 a 类型为 T,它没有移动构造函数。数组
在以前《构造函数语义学——Default Constructor 篇》一文中,咱们分析了编译器产生 default constructor 的条件,以及编译器所产生的 default constructor 的类型(trivial & non-trivial);对于构造函数来讲,其原理也是大体相似的,只是具体的细节条件不一样,此文中就再也不给出具体的证实,读过前一篇博文的读者也应该可以本身分析,此文只给出具体的条件函数
隐式声明的复制构造函数
若不对类类型(struct、class 或 union)提供任何用户定义的复制构造函数,则编译器始终会声明一个复制构造函数,做为其类的非 explicit 的 inline public 成员。spa
与 default constructor 相似,只要没有任何 user_declared 的 copy constructor,那么编译器就会为咱们自动声明一个 copy constructor(这一点与《深度探索 C++对象模型》中所述不一样)指针
隐式定义的复制构造函数
若隐式声明的复制构造函数未被弃置,则当其被 ODR 式使用时,它为编译器所定义(即生成并编译函数体)。对于 union 类型,隐式定义的复制构造函数(如同以 std::memmove)复制其对象表示。对于非联合类类型(class 与 struct),该构造函数用直接初始化,按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制。code
编译器自动合成的 copy constructor 也是分为 trivial 和 non-trivial 的对象
对于 trivial copy constructor 的条件,cppreference 中也给出了详细的说明:继承
当下列各项所有为真时,类 T 的复制构造函数为平凡的:
它不是用户提供的(即它是隐式定义或预置的),且若它被预置,则其签名与隐式定义的相同;
T 没有虚成员函数;
T 没有虚基类;
为 T 的每一个直接基类选择的复制构造函数都是平凡的;
为 T 的每一个类类型(或类类型数组)的非静态成员选择的复制构造函数都是平凡的;递归
而在《深度探索 C++对象模型》中有一句话“决定一个copy constructor是否为trivial的标准在于class是否展示出所谓的bitwise copy semantics”
;即若是一个 class 展示出了 bitwise copy semantics,那么编译器为其合成的 copy constructor 就是 trivial 的
换言之,若是不知足 bitwise copy semantics,那么编译器合成的 copy constructor 就是 non-trivial 的。什么时候一个 class 不表现出 bitwise copy semantics 呢?书中给了四个条件(略有修改):
其实这个四个条件至关于 cppreference 中提到的成为 trivial copy constructor 的相反条件
关于 trivial copy constructor 的行为,cppreference 也有提到:
非联合类的平凡复制构造函数,效果为复制实参的每一个标量子对象(递归地包含子对象的子对象,以此类推),且不进行其余动做。不过不须要复制填充字节,甚至只要其值相同,每一个复制的子对象的对象表示也没必要相同。
这句话的意思是说,若是编译器合成的出来 copy constructor 是 trivial 的,它展示出这种行为:逐个字节的拷贝全部内容
举个例子:
class A { private: int _a; }; int main() { A a; A aa = a; return 0; }
其中 A aa = a;这一句,会调用编译器产生的 trivial copy constructor,该 trivial copy constructor 会一个字节一个字节的把 a 中的成员变量的值拷贝到 aa 对应的成员变量中去
这彷佛看起来挺好的呀,也正是咱们所须要的结果,可是,若是 class A 中的成员变量是一根指针,那么问题就大了:
#include <iostream> using namespace std; class A { public: int *p; }; int main() { A a; int val = 1; a.p = &val; A aa = a; cout << a.p << endl; cout << aa.p << endl; *(aa.p) = 2; cout << *(a.p) << endl; cout << *(aa.p) << endl; } // 上述程序的输出为 0x7ffc5d760414 0x7ffc5d760414 2 2
也就是说,在编译器自动为咱们合成的 trivial copy constructor 的行为中,复制了 a 的指针给了 aa(浅拷贝),也就是说 a 和 aa 中的指针 p 指向了相同的地址!!!
在这种含有指针的状况下,编译器产生的 trivial copy constructor 的行为便不是咱们所但愿的,咱们必须手动显示的定义一个符合咱们需求的 copy constructor 来完成对指针的拷贝
cppreference 中已经说了:
对于非联合类类型(class 与 struct),该构造函数用直接初始化,按照初始化顺序,对对象的各基类和非静态成员进行完整的逐成员复制。
non-trivial copy constructor 一个很重要的行为是:确保 vptr 的准确设定。(由于只要包含虚机制,那么编译器自动合成的 copy constructor 就不多是 trivial 的)
上面一点,书中已经说的足够清楚,此文再也不赘述