看完ISO C++ 12.8一节后,做如下笔记。当一个类对象被用来初始化或赋值时会通过该类的复制构造或重载赋值操作符被复制。复制构造函数1.类A的复制构造函数不能是模板,模板复制构造函数不会阻止默认复制构造的生成。复制构造函数可以有不止一个参数,但不论有几个参数,其第一个参数必须是A &、const A &、volatile A &或const volatile A &型之一(均不能为非引用型),其他形参必须有默认实参,且默认实参必须出现在复制构造函数的声明中。这是因为对于:classX {public: X(const X&, int);};X::X(const X& ,int = 9){}因为如果没有在类的定义体中没有显示的声明一个复制构造函数,编译器将自动隐士声明一个。所以尽管在实现中对第二个参数提供了默认实参,但由于类中没有显示的声明复制构造函数,于是编译器便认该类中没有声明复制构造,所以将隐士的声明一个。这样一来,任何对该类的复制构造的调用都将使用编译器自己生成的。例如下面代码
2.如果类T的每一个直接基类或虚基类的复制构造函数的第一个参数类型中含有const限定符,且对于类T中所有类类型的非静态数据成员,当该数据成员所属的类中的复制构造函数的第一个参数类型中含有const限定符时,T类的隐士声明的复制构造函数的格式为T::T(const T&)。否则T类的隐士声明的复制构造函数的格式为T::T(T&)同时,隐式声明的复制构造函数是inline public。同时隐式声明的复制构造函数拥有异常说明。P.S.: 隐式声明的复制构造函数的参数类型中不包含volatile限定符,即只能是T& 或const T & ,不能是volatile T& 或volatile const T&。
3.当类T中有隐士声明的复制构造函数,且没有虚函数、没有虚基类、含有trivial复制构造函数的基类以及该类T的非静态类类型数据成员所属的类中有trivial复制构造函数时,则称T的复制构造函数为trivial,否则为nontrivial
4.当隐士声明的复制构造函数用于对该函数所属类的对象进行初始化时将被隐士定义,即便编译器优化掉对复制构造函数的调用。一个有隐士定义的复制构造函数的类T,如果满足以下情况之一时则是不规范的: 该T类的非静态类类型数据成员中有不可访问的或存在二义性的复制构造函数; 该T类的基类中有不可访问的或存在二义性的复制构造函数时。在一个隐式声明的复制构造函数被隐士定义之前,其所属类的直接/虚基类和非静态数据成员中隐式声明的的复制构造函数都应完成隐式定义
5.隐式定义的复制构造函数对类中的子对象将执行深拷贝(memberwise copy)。执行顺序与用户自定义中基类和成员的初始化顺序一样。执行过程:对类对象,调用该类中的复制构造函数;对内置数据类型,调用内置的赋值操作符另,隐士定义的复制构造函数应该只复制虚基类一次。
拷贝赋值操作符1.类T的一个用户定义的拷贝赋值操作符是一个非静态非模板成员函数(模板的赋值操作符并不会阻止编译器自行隐士生成赋值操作符函数),该函数只能有一个形参(即便多出的参数有默认实参)且形参类型是T &、const T &、volatile T &或const volatile T &之中的一个。可以存在多个赋值操作符。如果没有显示的声明拷贝赋值操作符,编译器将隐士生成一个。同时,对于该隐士生成的拷贝赋值操作符来说,如果类T中的每一个直接基类的拷贝赋值操作符的形参类型中含有const限定符,且如果类T的所有非静态类类型数据成员所属类中的拷贝赋值操作符的形参类型中含有const限定符,则类T的拷贝赋值操作符的形参类型为const T &,否则为T &.与隐士声明的复制构造函数一样,隐士声明的赋值操作符是inline public。另,因为拷贝赋值操作符的隐士声明是在用户没有声明时进行的,以及派生类的赋值操作符会隐藏基类的赋值操作符。所以,通过using声明从派生类的基类中引入一个参数类型为该派生类的赋值操作符仍会被该派生类中隐式声明的赋值操作符所隐藏。如下代码:
2.与复制构造函数一样,当一个类有隐式声明的拷贝赋值操作符,并且没有虚函数、没有虚基类、每一个直接基类有一个trivial拷贝赋值操作符、且所有非静态类类型数据成员所属的类中有一个trivial拷贝赋值操作符时,该隐式声明的拷贝赋值操作符为trivial,否则其他情况下都为nontrivial。
3.当有用到赋值操作符时隐士声明的赋值操作符将隐士定义。对于有隐士定义的赋值操作符的类,如果该类有const限定的非静态数据成员、或有非静态数据成员的引用类型、或有非静态类类型的数据成员所属的类中的有不可访问的赋值操作符,或有基类中有不可访问的赋值操作符时,是不规范的。同时在隐士声明的赋值操作符被隐士定义之前,该类中的所有基类以及非静态数据成员都应该完成隐士定义。另,隐士声明的赋值操作符有异常说明符。
4.隐士定义的赋值操作符执行深度赋值(memberwise assignment)。对基类的赋值顺序与基类派生列表中的顺序一样,而非静态数据成员则以他们在类中定义的顺序赋值。每个子对象的赋值都是以特定的方式进行:如果子对象为类类型,则调用其所属类中的赋值操作符;如果是内置类型,则使用内置的赋值操作符注意:并没有说明隐式定义的赋值操作符对虚基类赋值几次参考:ISO C++ 12.8