现在的位置: 首页 > 综合 > 正文

C++ Primer学习笔记——$13 拷贝控制

2018年03月30日 ⁄ 综合 ⁄ 共 2253字 ⁄ 字号 评论关闭

题记:本系列学习笔记(C++ Primer学习笔记)主要目的是讨论一些容易被大家忽略或者容易形成错误认识的内容。只适合于有了一定的C++基础的读者(至少学完一本C++教程)。

 
作者: tyc611, 2007-01-29


   本文主要讨论C++类定义中的拷贝控制(copy control):拷贝构造函数、赋值操作符和析构函数。
   如果文中有错误或遗漏之处,敬请指出,谢谢!


   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):如果类需要自定义析构函数,则它往往也需要拷贝构造函数和赋值操作符。
 
   合成的析构函数按成员在类中声明次序的逆序来撤销成员。
 
   析构函数与拷贝构造函数或者赋值操作符之间的一个重要区别是:即使我们编写了自己的析构函数,在自定义析构函数运行结束后,合成析构函数仍然将继续运行,它来完成成员数据的撤销工作。
 

   如果文中有错误或遗漏之处,敬请指出,谢谢!


 

参考文献:
[1] C++ Primer(Edition 4)
[2] Thinking in C++(Volume Two, Edition 2)
[3] International Standard:ISO/IEC 14882:1998

抱歉!评论已关闭.