“= default”和 “= deleted”
C++11 标准把
“= default” 修饰的函数称为显式缺省(explicated faulted)函数,把 “= delete”
修饰的函数成为为删除函数,为了方便也将它称为显示删除函数。C++11 引入显式缺省和显式删除是为了增强对类默认函数的控制,让程序员能够更加精细地控制类默认函数。
为了更好的了解“= default”和 “= deleted”,我们先来了解一下类默认函数。
类默认函数
在声明自定义类的时候,C++ 编译器会默认帮助程序员生成一些未自定义的成员函数,这些函数被称为“默认函数”。默认函数包括以下自定义类型的成员函数:
n 构造函数
n 拷贝构造函数
n 拷贝赋值函数( operator =)
n 移动构造函数(C++11 标准新增)
n 移动拷贝函数(C++11 标准新增)
n 析构函数
同时,C++ 编译器还会为一些自定义类型提供全局默认操作符函数,这些操作符函数包括以下几种:
n operator
n operator &
n operator &&
n operator *
n operator ->
n operator ->*
n operator new
n operator delete
显示缺省和显示删除函数就是为了更好地控制和操作类默认函数而提出的,因此在学习之前非常有必要了解什么是类默认函数以及类默认函数包括那些。
显示缺省
在 C++ 中程序员如果实现了类默认控制函数自定义的版本,那么编译器就不会再为该类自动生成默认版本的函数。这样的规则有时候会带来一个比较严重的问题:程序中一旦声明了自定义版本的构造函数,可能会导致我们类不再是 POD 类型。
关于 POD 类型请读者自行参考《非受限联合体》一文,POD 类型是 C++11 中很重要的一个概念,使用 POD类型有很多好处,比如统一列表初始化、编译器在遇到 POD 类型时会对其进行优化等。
我们通过具体的例子来分析一下以上的问题:
class People {
public:
People()
{ }
People(int
n): num(n){}
private:
int
num;
};
在上面的例子中我们可以看到,我们只是想初始化 num 这个类成员变量,却因此使类 People 不再是 POD 类型。这种做法会使编译器失去优化数据类型的可能,这是程序员不希望看到的结果。
为了使 People 恢复 POD 特质,C++11 提出的新的机制来控制默认版本函数的生成。新的机制重用了 default 这个关键字。
程序员可以在默认函数定义或者声明时加上“= default”,从而显式地指示编译器生成该函数的默认版本。同时,如果使用
“default”后,就不必在实现一份同名函数,请看下面的例子:
class People {
public:
People()
= default;
People(int
n): num(n){}
private:
int
num;
};
上面的例子中使用“= default”修饰构造函数 People(),从而显示地使编译器生成默认构造函数 People(),这样就可以使类 People 重新成为一个 POD 类型。
“= default”不仅可以在类定义内修饰类成员函数,还可以在类定义外修饰类成员函数,不仅可以修饰编译器默认提供的缺省函数,还可以修饰一些其他函数,比如“operator ==”。但是,无论修饰什么样的函数“= defalut”的用法都是不变的。
显示缺省的应用
上文提到显示缺省可以在修饰类外定义的成员函数,这种做法有时可以方便程序员做一些代码调试。我们可以具体地看一下怎么做的:
首先,假设我们有一个
type.h 的文件,文件中声明了一个类:
class type { type(); };
除此之外我们再创建两个
.cpp 文件,分别用来实现类 type 的缺省函数版本和自定义函数版本:
type1.cpp : type::type() = default ;//实现缺省函数的版本
tyep2.cpp : type::type() { //do something }
//实现自定义函数版本
之后我们可以有选择地编译 type1.cpp 和 type2.cpp 文件,实现在缺省函数的版本和使用自定义版本的函数间进行切换,达到调试代码的目的。
显示删除
与“= default”相对,“= delete”则是禁止类的使用者使用默认函数。比如说,如果类的编写者需要禁止使用者使用拷贝构造函数,这时候要是使用“= delete”来修饰拷贝构造函数就会很方便。
接下来,我们通过一个例子来看一下如何通过
“= delete”修饰拷贝构造函数来禁止类的使用者使用拷贝构造函数。
#include
using namespace std;
class NoCopyPeople{
public:
pyPeople()
= default;
NoCopyPeople(const NoCopyPeople &) = delete; //禁止使用该函数
};
int main(){
NoCopyPeople a;
NoCopyPeople b(a); //无法通过编译
return 0;
}
上例中由于使用 “= delete” 删除了拷贝构造函数,这样编译器就不会生成缺省拷贝构造函数。从中我们可以看出,显示删除可以避免用户使用一些不应该使用的类的成员函数。
同显式缺省一样,显示删除也可以应用除了类成员函数外的其他函数,比如全局函数,甚至是普通函数。这时显示删除可以用来禁止一些隐式的类型转换。
显示删除的应用
上文我们说到显示删除可以禁止一些隐式的类型转换,这样有助于程序的安全。接下来我们就具体的来看一个例子:
#include
using namespace std;
class People{
public:
People(int n){};
Peopele(char c) = delete;
};
int main(){
People p1(3);
People p2(‘a’); //无法通过编译
return 0;
}
在上面的例子中由于我们显示地删除了 People(char c) 这个版本的构造函数,则在我们构造变量 p2 时就无法通过编译。与此形成对比的是,再没有
“= delete”时或者不使用“= delete”时,编译器会隐式地将 a 转换为整型,并调用整型版本的构造函数,这样的作法会影响到程序的安全性。
同样的,“= delete”作用在普通函数时,也可以实现相同的功能。接下来我们再看一下“= delete”作用在普通的函数中实现禁止隐式的类型转换,示例如下:
#include
using namespace std;
void Func(int i){};
void Func(char c) = delete;
int main(){
Func(3);
Func(‘a’); //无法通过编译
return 0;
}
上面的例子 Func() 虽然是普通函数但依然可以使用“= delete”来修饰,同样也达到了禁止编译器做隐式类型转换的作用。
加粗样式