C++11中新增了两个关键字default和delete。这两个关键字主要用于解决类继承关系中的构造函数与拷贝构造函数显示定义的问题。我们知道,编译器会为一个新类定义6个(以前是4个,C++11后是5个)默认的构造函数,它们分别是:
- 构造函数
- 拷贝构造函数
- 移动拷贝构造函数
- 拷贝赋值函数
- 移动赋值函数
- 析构函数
举个例子:
class Zoo{
public:
Zoo(){//...}
Zoo(const Zoo&){//...}
Zoo(Zoo&&){//...}
Zoo& operator=(const Zoo&){}
Zoo& operator=(const Zoo&&){}
virtual ~Zoo(){}
};
对于一个类,编译器会为我们自动生成上述6个默认函数。但是,如果我们定义了自己的函数实现方式,那么编译器就会摒弃默认的函数,使用我们定义好的函数替换该默认函数。
然而在实际使用中,这个机制会造成某些不必要麻烦。比如我们在子类中希望使用到父类的默认构造函数,但是却被父类的自定义构造函数替换了等等。同时,有时我们希望某些类不允许进行拷贝构造操作,此时可以考虑使用这两个关键字。
下面讨论在什么情况下使用这两个关键字是合法的。
class Foo{
public:
Foo(int i):_i(i){}
Foo() = default; //构造函数可以多个并存
Foo(const Foo&X):_i(X._i){}
//!Foo(const Foo&) = default; //只允许存在一个拷贝构造函数
//!Foo(const Foo&) = delete; //编译器报错为如下
/*'Foo::Foo(const Foo&)' cannot be overloaded with 'Foo::Foo(const Foo&)'不允许被重载*/
Foo& operator=(const Foo&x){_i = x._i;return *this;}
//! Foo& operator=(const Foo& x) = default;
//! Foo& operator=(const Foo& x) = delete;
//这个和上面的拷贝构造函数同理,不允许被重载,也就是只允许有一个这样的函数。如果自定义了自己的拷贝赋值函数,那么默认的函数就自动消失了,因此这种情况下使用关键字会报错。
//! void func1() = default; // 普通函数无法使用default关键字
void func2() = delete; //ok 任何函数都允许使用使用delete,前提是该函数存在且不允许被重载。
//! ~F00() = delete; // 这个不用说,类肯定得有自己的析构函数
~Foo() = default; //这个是合理的,构造函数和析构函数都允许多个重载存在
private:
int _i;
};
从上述总结中,大概可以总结出以下几条使用规律:
- default几乎只用于开启默认的构造函数或者析构函数。(按照侯捷老师的话说,default用于Big-Five(编译器指定的默认函数)之外无意义。)
- delete用于关闭类不需要的函数功能,这个函数一般是指编译器指定的默认函数。
- 针对拷贝构造函数、移动拷贝构造函数、拷贝赋值函数、移动拷贝赋值函数,如果定义自己的函数,则delete不可用。因为这几个函数不允许存在多个重载版本。
- 如果构造函数和析构函数没有被重载,则delete不可用,因为每个类必须至少有构造函数和析构函数。
注:
编译器给定的默认函数都做了些什么呢?default ctor(构造函数)和dtor(析构函数)主要是给编译器一个地方用来放置[藏身幕后]的code,像是唤起base classes以及non-static members的ctors和dtors。
编译器产生的dtor是non-virtual,除非这个class的base class本身宣告有virtual dtor.
至于copy ctor 和 copy assingment operator。编译器合成版只是单纯将source object的每一个non-static members拷贝到destination object.