拷贝控制

 
C++ 类中有四个不可或缺的部分,那就是构造函数、拷贝构造函数、赋值操作符和析构函数。如果类中没有定义这些函数,那么编译器将为类自动生成这些函数。当然,也可以通过 private 控制策略限定不使用拷贝构造函数和赋值操作符。
 
   其中,拷贝构造函数、赋值操作符和析构函数总称为拷贝控制( copy control )。
 
   当类中有指针类数据成员时,一般都需要自已实现类的拷贝控制。通常有两种处理策略:一是定义值型类,每个类保留一份指针指向的对象的拷贝;另一种更常用的策略是使用智能指针 (smart pointer) ,其通用技术是采用引用计数 (reference count) 来实现共享指针指向的对象。
 
拷贝构造函数
   我们知道 C++ 中变量初始化有两种形式:直接初始化和拷贝初始化。直接初始化将初始化放在圆括号中,而拷贝初始化使用 = 符号。对于内置类型,这两者基本上没有区别。但对于类类型,两种方式是有区别的:直接初始化直接调用与实参匹配的构造函数;而拷贝初始化总是调用拷贝构造函数,具体而言,就是拷贝初始化首先使用指定构造函数创建一个临时对象,然后用拷贝构造函数将那个临时对象拷贝到正在创建的对象。
 
   支持拷贝初始化主要是为了与 C 的用法兼容。当情况允许时,可以允许编译器跳过拷贝构造函数直接创建对象,但编译器没有义务这样做。注:事实上大多数编译器都跳过了拷贝构造函数,因为这完全可以跳过,比如在 VC6.0 MinGW2.05
 
   我们知道可以用表示容量的单个参数来初始化容器,容器的这种构造方式使用了默认构造函数和拷贝构造函数。例如:
   vector<string> svec(5);
编译器首先使用 string 的默认构造函数创建一个临时值来初始化 svec ,然后使用拷贝构造函数将临时对象拷贝到 svec 的每个元素。示例代码如下:

#include <iostream>
#include <vector>
using namespace std;

class Test {
public:
    Test() {
        cout<<"Contructor"<<endl;
    }
    Test(const Test& t) {
        cout<<"copy Contructor"<<endl;
    }
};

int main() {
    vector<Test> tvec(5);
    return 0;    
}
 
输出结果为:( MinGW 2.05 VC6.0
Contructor
copy Contructor
copy Contructor
copy Contructor
copy Contructor
copy Contructor

   对于元素为类类型的数组,可以使用数组初始化列表来提供显示元素初始化。此时,使用拷贝初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用拷贝构造函数将该值拷贝到相应元素。当然,同前面一样,是否跳过拷贝构造函数取决于编译器(事实上,大多数编译器跳过了这步)。
 
   拷贝构造函数的形参是一个类类型引用(否则,参数本身就需要拷贝构造函数了),但一般情况下,我们使用 const 修饰。并且,一般不应该设置为 explicit
 
   有时需要禁止拷贝类,例如, iostream 类就不允许拷贝。这时,应当显示声明拷贝构造函数为 private ,此时可以不定义该函数。若声明为 private 且进行了函数定义,则类的友元和成员仍然可以进行拷贝。
  注意:声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试将导致链接失败。  

合成的拷贝构造函数
    如果我们没有定义拷贝构造函数,则编译器会自动生成一个,把这个自动生成的拷贝构造函数叫合成的拷贝构造函数( Synthesized Copy Constructor )。合成的拷贝构造函数执行逐个成员初始化,将新对象初始化为原对象的副本。如果成员是内置类型,则执行位拷贝;如果成员是类类型,则调用相应的拷贝构造函数;如果成员是数组类型,则分别对每个数组元素进制拷贝。
 
赋值操作符
   赋值操作符的右操作数一般以 const 引用传递,为了与内置类型的行为一致,常返回该类类型的引用。通常,拷贝构造函数和赋值操作符是同时出现的,定义了一个就应当定义另一个。
 
 赋值操作符一个必须小心的地方时检查自赋值。
 
析构函数
   三法则( rule of three ):如果类需要自定义析构函数,则它往往也需要拷贝构造函数和赋值操作符。
 
   合成的析构函数按成员在类中声明次序的逆序来撤销成员。
 
   析构函数与拷贝构造函数或者赋值操作符之间的一个重要区别是:即使我们编写了自己的析构函数,在自定义析构函数运行结束后,合成析构函数仍然将继续运行,它来完成成员数据的撤销工作。
 

你可能感兴趣的:(拷贝控制)