如果想让你的类定义出来的对象是独一无二的,即对象无法被复制,或者使用赋值操作符赋给另外一个对象,那么最好的方法就是禁用拷贝构造函数和赋值操作符。下面介绍几种禁用的方法。(方法来自Effective C++,如果想禁用类的其他函数,方法类似)
1. 定义为private且不实现它
我们知道,拷贝构造函数和赋值操作符重载函数,即使不定义,编译器也会生成一个默认的函数。但是如果定义了,class还是会支持这两个函数。那么应该怎么去禁用它们呢?
在C++中,如果不想让对象调用某个方法,那么可以将这个方法声明为private,因此,我们可以将拷贝构造函数和赋值操作符重载函数声明为private,就可以阻止别人调用它了。但是,private函数还是可以在类的成员函数和friend函数中调用。这里还有一个技巧,就是将函数定义为private而且不实现它。
class SpecialClass {
...
private:
SpecialClass(const SpecialClass&);
SpecialClass& operator=(const SpecialClass&);
};
将SpecialClass这样定义,并且不实现这两个函数,那么如果有人试图拷贝这个类的对象,编译器就会报错:
SpecialClass sc1;
SpecialClass sc2(sc1); //error, 编译出错
SpecialClass sc3;
sc3 = sc1; //error, 编译出错
而且即使在成员函数或者friend函数中调用了它,链接时也会出错。
2. 继承Uncopyable类
第一种方法可以解决禁用拷贝构造函数的问题,但是还有一点不完美的是,如果用户不小心在成员函数或者friend函数中调用了它,那么这个错误只有到链接阶段才能被发现。所以就有了继承一个Uncopyable类的方法:
class Uncopyable {
protected:
Uncopyable() {}
~Uncopyable() {}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
};
此时,只需要继承Uncopyable类就可以防止其对象被拷贝了。其原理是:SpecialClass继承了Uncopyable函数,它的对象如果调用了拷贝构造函数,或者使用了赋值操作符,那么编译时,就会去调用基类的对应函数,但是基类里的这两个函数是private的,所以编译会失败。
另外,boost也提供了一个Uncopyable函数,那个class名字为noncopyable,因此我们也可以这样做:
#include <boost/utility.hpp>
class SpecialClass : boost::noncopyable {
...
};
3. C++0x中的新方法
在C++0x中,新添加了两个关键字:default和delete,可以用于控制某个类使用默认函数(default)或者禁用某个函数(delete)。其中,使用default关键字可以将某个函数定义为默认函数。如:
class SpecialClass {
...
public:
SpecialClass(const SpecialClass&) = default;
SpecialClass& operator=(const SpecialClass&) = default;
};
可能这样写有点多余,但是它可以直观表示“我使用的默认的函数”。而且,如果有人想显式定义默认函数,但是自己写的代码又可能有错误,就可以使用这个关键字,方便且不出错。
delete关键字可以禁用掉类的方法,正如我们上面前两节所做的事情,就可以用这一个关键字完成:
class SpecialClass {
...
public:
SpecialClass(const SpecialClass&) = delete; //禁用拷贝构造函数
SpecialClass& operator=(const SpecialClass&) = delete;
};
default关键字可以用于一切含有默认方法的函数。delete关键字可以用于类的所有方法,例如可以禁用掉函数的某些类型的参数:
class SpecialClass {
...
public:
void fn(long);
void fn(int) = delete;
};
SpecialClass sc;
long l;
sc.fn(l); //ok, 调用void fn(long);
int i;
sc.fn(i); //error, 调用void fn(int);
上面的代码会将void fn(int)类型的函数禁用掉,而如果不禁用的话,sc.fn(i)会进行隐式转换调用void fn(long)函数。
参考:
文章摘自 xd_xiaoxin 的博文。