高质量程序设计指南c++/c语言(35)--复制控制

      复制构造函数、赋值操作符和析构函数总称为复制控制(copy control),编译器会自动实现这些操作,但是类也可以定义自己的版本。通常,编译器合成的复制控制函数是非常精炼的--它们只做必须的工作。但对某些类而言,依赖于默认定义会导致灾难。实现复制控制操作最困难的部分,往往在于识别何时需要覆盖默认版本。有一种特别常见的情况需要类定义自己的复制控制函数,那就是类具有指针成员。

 1、复制构造函数

string null_book = "66666"; //首先调用一个接受c风格字符串形参的string构造函数,创建一个临时对象,然后,编译器使用string复制构造函数将null_book初始化为那个临时对象的副本。

ifstream file1("filename"); // ok:direct initilization

ifstream file2 = "filename"; // error:copy constructor is private

Sales_item item = string("3333"); //this initilization is ok only if the Sales_item(const string&) constructor is not explicit

1.1构造函数与数组元素

#include<iostream>
using namespace std;

class A
{
public:
    A()
    {
        data = 0;
        cout << "A()" << endl;
    }
    A(const A &a)
    {
        data = a.data;
        cout << "A(const A &a)" << endl;
    }
    A(int _data)
    {
        data = _data;
        cout << "A(int _data)" << endl;
    }

private:
    int data;
};

int main(void)
{
    A a[4] = {1, 2, A(4)};

    return 0;
}

输出:

A(int _data)   //a[0]
A(int _data)   //a[1]
A(int _data)   //a[2]
A(const A &a)  //a[2]
A()            //a[3]

1.2初始化容器元素与构造函数

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

class A
{
public:
    A()
    {
        data = 0;
        cout << "A()" << endl;
    }
    A(const A &a)
    {
        data = a.data;
        cout << "A(const A &a)" << endl;
    }


private:
    int data;
};

int main(void)
{
    vector<A> b(4);  //default A constructr and four copy constructrs are invoked
    return 0; 
}

输出:

A()
A(const A &a)
A(const A &a)
A(const A &a)
A(const A &a)

 2、合成的赋值构造函数

      如果我们没有定义复制构造函数,编译器就会给我们合成一个。与合成的默认构造函数不同,即使我们定义了其他的构造函数,编译器也会合成复制构造函数。合成复制构造函数(synthesized copy constructor)的行为是,逐个成员初始化(memberwise initialize),将新对象初始化为原对象的副本。所谓“逐个成员”,指的是编译器将现有对象的每个非static成员,依次复制到正创建的对象。

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

3、禁止复制

     为了防止复制,类必须显示声明其复制构造函数为private。如果复制构造函数时私有的,则不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制尝试。 

    然而,类的友元和成员函数仍可进行复制。如下面:

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

class A
{
public:
    A()
    {
        data = 0;
        cout << "A()" << endl;
    }
    friend void f(const A &a);
private:
    A(const A &a)
    {
        data = a.data;
        cout << "A(const A &a)" << endl;
    }
    int data;
};

void f(const A &a)
{
    A b(a);   //友元函数内仍然可以使用私有的复制构造函数
    cout << a.data << endl;
}

int main(void)
{
    A a;
    A b(a);  //编译错误 

    return 0;
}

如果想要连友元和成员中的复制也禁止,就可以声明一个private复制构造函数但不对其进行定义。声明而不定义成员函数是合法的,但是,使用未定义成员的任何尝试都将导致连接失败。通过声明但不定义private构造函数,可以禁止任何复制类类型对象的尝试:用户代码中的复制尝试将在编译时发生错误,而成员函数和友元中的复制尝试将在连接时出错。

4、赋值操作符

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

class A
{
public:
    A()
    {
        data = 0;
        cout << "A()" << endl;
    }
    A(int _data)
    {
        data = _data;
        cout << "A(int _data)" << endl;
    }
    A(const A &a)
    {
        data = a.data;
        cout << "A(const A &a)" << endl;
    }
    void print()
    {
        cout << "data:" << data << endl;
    }
private:
    int data;
};

class B: public A
{
public:
    B(int data):A(data)
    {
        data1 = data;
        cout << "B(int data):A(data)" << endl;
    }
    B(const B &b)
    {
        data1 = b.data1;
        cout << "B(const B &b)" << endl;
    }
    void print()
    {
        A::print();
        cout << "data1:" << data1 << endl;
    }
private:
    int data1;
};



int main(void)
{
    B b1(1);
    b1.print();
    cout << endl;

    B b2(2);
    b2.print();
    cout << endl;

    b1 = b2;
    b1.print();
    cout << endl;

    return 0;
}

输出:

A(int _data)
B(int data):A(data)
data:1
data1:1

A(int _data)
B(int data):A(data)
data:2
data1:2

data:2
data1:2

即如果我们没有自定义赋值操作符,则编译器会为我们合成一个赋值操作符,它会把该类以及所有的父类的成员逐个复制。如果我们重写了子类的赋值操作符,但是在子类的赋值操作符内没有调用父类的赋值操作符的话,那么只会发生子类的成员赋值,父类的成员没有进行赋值。

    void operator=(const B &b)
    {
        data1 = b.data1;
        cout << "void operator=(const B &b)" << endl;
    }
//在子类B中加入赋值运算符重载函数,输出如下
data:1
data1:2

所以需要显示的调用父类的赋值运算符。

    void operator=(const B &b)
    {
        A::operator=(b);
        data1 = b.data1;
        cout << "void operator=(const B &b)" << endl;
    }
//显示调用父类A的赋值运算符,输出如下:
data:2
data1:2
至于父类A要不要重写赋值运算符,在本例中是无所谓的,因为如果我们不重写的话,编译器会自动合成一个。

 

posted on 2013-05-13 17:27  江在路上2  阅读(208)  评论(0编辑  收藏  举报