google C++ 编程规范中的禁用复制构造函数和赋值运算符

时间:2021-07-04 19:25:54

在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


当执行下面的main函数的时候:
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类型或者其他智能指针类型。