C++ Primer 学习笔记_54_类与数据抽象 --复制构造函数、赋值操作符(一)

2014-11-24 12:20:02 · 作者: · 浏览: 1

复制控制

--复制构造函数、赋值操作符



引言:

当定义一个新类型时,需要显式或隐式地指定复制、赋值和撤销该类型的对象时会发生什么 复制构造函数、赋值操作符和析构函数的作用!

复制构造函数:具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式的使用复制构造函数;当将该类型的对象传递给函数或者从函数返回该类型的对象时,将隐式使用复制构造函数。

析构函数:作为构造函数的互补,当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。

赋值操作符:与构造函数一样,复制操作符可以通过指定不同类型的右操作数而重载。右操作数为类类型的版本比较特殊:如果我们没有编写这种版本,则编译器将为我们合成一个。

【小心地雷】

通常,编译器为我们合成的复制构造函数函数是非常精炼的---它们只做必须的工作,但对于类而言,依赖于默认定义有时会导致灾难!


一、复制构造函数

只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为复制构造函数。与默认构造函数一样,复制构造函数可由编译器隐式调用。复制构造函数可用于:

1)根据另一个同类型的对象显式或隐式初始化一个对象。

2)复制一个对象,将它作为实参传给一个函数。

3)从函数返回时复制一个对象。

4)初始化顺序容器中的元素。

5)根据元素初始化式列表初始化数组元素。


1、对象的定义形式

对于类类型,初始化的复制形式和直接形式有所不同:直接初始化直接调用与实参匹配的构造函数,复制初始化式总是调用复制构造函数。[复制初始化首先使用指定构造函数创建一个临时对象,然后复制构造函数将那个临时对象复制到正在创建的对象!]

    //理解下列语句的区别
    string null_book = "9-99999-999-9";
    string dots(10,'.');
    string empty_copy = string();
    string empty_direct;

对于类类型对象,只有指定单个实参或显式创建一个临时对象用于复制时,才使用复制初始化!

支持初始化的复制形式主要是为了与C的用法兼容,当情况许可时,可以允许编译器跳过复制构造函数函数直接创建对象,但是编译器没有义务这样做!

通常直接初始化和复制初始化仅在低级别上存在差异。然而,对于不支持复制的类型,或者使用非explicit构造函数的时候,它们有本质区别:

    ifstream file1("filename"); //OK
    ifstream file2 = "filename";//Error,由于IO类型的对象不能复制

    Sales_item item = string("9-99999-999-9");

2、形参与返回值

当形参为非引用类型的时候,将复制实参的值,类似的,以非引用类型作返回值时,将返回return语句中值的副本。

//当形参/返回值为类类型时,由复制构造函数进行复制。
//然而该函数的形参是const的引用,因此不会复制
string make_plural(size_t,const string &,const string &);

3、初始化容器元素

//首先使用string默认构造函数创建一个临时值来初始化svec
//然后使用复制构造函数将临时值复制到svec的每个元素
vector
  
    svec(5);

  

【推荐】

作为一般规则,除非你想使用容器元素的默认初始值,更有效的办法是,分配一个空容器并将已知元素的值加入容器。


4、构造函数与数组元素

如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。然而,如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素:

    Sales_item arrItem[] = {string("0-201-16487-6"),
                            string("0-201-54848-6"),
                            string("0-201-82470-6"),
                            Sales_item()
                           };

合成的复制构造函数

合成复制构造函数的行为是:执行逐个(非static)成员初始化,将新对象初始化为原对象的副本!

合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。数组成员的复制是个例外。虽然一般不能复制数组,但如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。

逐个成员初始化最简单的概念模型是,将合成复制构造函数看作这样一个构造函数:其中每个数据成员在构造函数初始化列表中进行初始化。

class Sales_item
{
public:
    Sales_item(const Sales_item &);
private:
    std::string isbn;
    int units_sold;
    double revenue;
};

Sales_item::Sales_item(const Sales_item &orig):
    isbn(orig.isbn),units_sold(orig.units_sold),revenue(orig.revenue) {}

定义自己的复制构造函数

class Foo
{
public:
    Foo();
    Foo(const Foo &);   //复制构造函数
};

复制构造函数的形参通常是一个const引用;因为由于向函数传递对象和从函数返回对象,该构造函数一般不应设置为explicit!

对许多类而言,合成复制构造函数只完成必要的工作。只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义复制构造函数,也可以复制。

然而,有些类必须对复制对象时发生的事情加以控制。这样的类经常有一个数据成员是指针或者有成员表示在构造函数中分配的其他资源,而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义复制复制构造函数!

通常,定义复制构造函数最困难的部分在于认识到需要复制构造函数O(∩_∩)O~。只要能认识到需要复制构造函数,定义构造函数一般非常简单。复制构造函数的定义与其他构造函数一样:它与类同名,没有返回值,可以(而且应该)使用构造函数初始化列表初始化新创建对象的成员,可以在函数体中做任何其他必要工作

//P410 习题13.4
class NoName
{
public:
    NoName():pstring(new std::string),i(0),d(0){}

    NoName(const NoName &temp):i(temp.i),d(temp.d)
    {
        pstring = new std::string;
        *pstring = *(temp.pstring);
    }

private:
    std::string *pstring;
    int i;
    double d;
};

禁止复制

有些类需要完全禁止复制。例如,iostream类就不允许复制。如果想要禁止复制,似乎可以省略复制构造函数,然而,如果不定义复制构造函数,编译器将合成一个。

通过声明但不定义private复制构造函数可以禁