在google C++编程规范中有下面一段描述:
仅在代码中需要拷贝一个类对象的时候使用拷贝构造函数;不需要拷贝时应使用
DISALLOW_COPY_AND_ASSIGN。
定义:通过拷贝新建对象时可使用拷贝构造函数(特别是对象的传值时)。
优点:拷贝构造函数使得拷贝对象更加容易,STL容器要求所有内容可拷贝、可赋值。
缺点:C++中对象的隐式拷贝是导致很多性能问题和bugs的根源。拷贝构造函数降低了
代码可读性,相比按引用传递,跟踪按值传递的对象更加困难,对象修改的地方变得难以捉
摸。
而具体的则没有明说,今天看了《More Effective C++》之后,重新思考了这个问题。
禁用复制构造函数的原因有下面两个(包括但不限于 :)):
1.耗时
当继承的层次越来越深的时候,对象会越来越大,采用浅复制的话,虽然速度快,但是会有内存泄漏和出现野指针等风险。采用深复制的话,速度就会慢很多。而赋值运算符也是同理。
2.不安全
当使用赋值运算符的时候,考虑下面一个例子(为了便于阅读,我把函数的定义和类的定义放在一起了):
#ifndef _HUMAN_H
#define _HUMAN_H
#include <cstdio>
class Human {
public :
Human &operator=(const Human &rhs){
printf("Human &operator=(const Human &rhs)\n");
return *this;
}
};
class Male : public Human{
public :
Male &operator= (const Human &rhs) {
printf("Male &operator= (const Human &rhs)\n");
return *this;
}
Male &operator=(const Male &rhs) {
printf("Male &operator=(const Male &rhs)\n");
return *this;
}
};
class Female : public Human {
public :
Female &operator= (const Human &rhs) {
printf("Female &operator= (const Human &rhs)\n");
return *this;
}
Female &operator=(const Female &rhs) {
printf("Female &operator=(const Female &rhs)\n");
return *this;
}
};
#endif // _HUMAN_H
int main() {
Human *male1 = new Male();
Human *male2 = new Female();
*male1 = *male2;
return 0;
}
调用的是Human &operator=(const Human &rhs)这个复制运算符。而当调用基类的赋值运算符的时候,只能被复制的也只是基类的部分,而真正的派生类的部分是不会被复制的,这不符合我们的语意。
如果把赋值运算符声明为虚函数的话,虽然能调用基类中的
virtual Male &operator= (const Human &rhs)。而这个动作还是跟我们的语意不相同,我们希望的是调用
virtual Male &operator=(const Male &rhs)。要做到这个其实也是可以的,不过需要付出一定的时间。使用在调用的函数中使用dynamic_cast就可以知道是传入的参数的动态类型是基类的还是派生类,然后选择正确的赋值运算符。但是dynamic_cast的时间效率实在是不好,特别是当继承层次变的深的时候。
综上所述,我们在定义一个类型的时候,如果不是必须,就尽量的禁用赋值运算符和复制构造函数。用来代替的可以声明这样一个基类,然后继承这个基类。
class NoCopyAndAssign {
public :
NoCopyAnd *clone() const = 0;
private :
NoCopyAndAssign(const NoCopyAndAssign &);
void operator=(const NoCopyAndAssign &);
};
通过clone返回一个指向已经复制的元素的指针,就可以了。如果想要更安全一点,可以把返回的指针包装在auto_ptr类型或者其他智能指针类型。