高质量程序设计指南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要不要重写赋值运算符,在本例中是无所谓的,因为如果我们不重写的话,编译器会自动合成一个。