C++11 =default与=delete

1. 缺乏控制的特殊成员函数

 C++中,如果没有显式定义某些特殊成员函数时,编译器会自动为类生成这些成员函数。例如:默认构造函数、析构函数、拷贝构造函数、赋值运算符重载函数等,这个特性让程序员可以有更多精力关注类本身的功能而不必为了某些语法特性而分心,同时也避免了让程序员编写重复的代码,比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Point
{
public:
int _x;
int _y;
};

int main()
{
Point p1; // 调用编译器自动生成的构造函数
Point p2(p1); // 调用编译器自动生成的拷贝构造函数
p2 = p1; // 调用编译器自动生成的赋值运算符重载函数

return 0;
} // 调用编译器自动生成的析构函数

当显式定义这些成员函数时,编译器不会自动生成,但这会在某些场景下带来问题。例如:我们想在定义Point对象时对_x、_y初始化,于是我们自己写一个构造函数,但代码中如有使用之前编译器生成默认构造函数则会编译失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point
{
public:
int _x;
int _y;

Point(int x, int y) : _x(x), _y(y) {}
};

int main()
{
Point p1; // 错误,显式定义了形参数量为2的构造函数,编译器不会生成默认构造函数
Point p2(1,2);

return 0;
}

如果想编写一个禁止复制操作的类,在C++11之前只能将拷贝构造函数与赋值运算符重载函数声明为
private并且不实现该函数。但是这种方式并不完美。如果某个类或函数是这个类的友元,那么可以访问该类私有的拷贝构造(赋值)函数,但是我们并没有实现这个函数,程序最后会在链接阶段报错。

1
2
3
4
5
6
7
8
9
10
11
class Point
{
public:
int _x;
int _y;

private:
Point(const Point &p);

Point &operator=(const Point &p);
};

在C++11之前程序员不能显式控制这些特殊成员函数的生成,而编译器对是否生成这些特殊成员函数有一套比较复杂的规则,所以造成了很大的不便

2. 显式默认和显式删除

C++11标准提供了一种方法告诉编译器是否生成或删除对应的默认成员函数。,我们将这种方法叫作显式默认和显式删除。显式默认和显式删除的语法非常简单,只需要在声明函数的尾部添加=default=delete

说明: =default只能适用于类的特殊成员函数,=delete可以适用于任何函数。因为类的特殊成员函数是明确做某事的,所以编译器能够帮你实现。但是对于普通函数来说,编译器不知道你这个函数要干嘛,所以不能够帮你实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Point
{
public:
int _x;
int _y;

Point() = default; // 生成默认构造函数

Point(int x, int y) : _x(x), _y(y) {}

Point(const Point &p) = delete; // 删除拷贝构造函数

Point &operator=(const Point &p) = delete; // 删除赋值运算符重载函数
};

int main()
{
Point p1; // 正确,调用编译器生成的默认构造函数
Point p2(1,2); // 正确,调用Point(int x, int y)构造函数
Point p3(p1); // 错误,拷贝构造函数已被删除
p1 = p2; // 错误,赋值运算符重载函数已被删除

return 0;
}

3. 特殊类设计

3.1 禁止类对象在堆上分配

如果想一个类的使用者只能通过局部变量、静态变量或者全局变量的方式创建对象,而不允许使用new运算符或new [ ]运算符在堆上开辟空间,可以将下列成员函数显式删除。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Point
{
public:
int _x;
int _y;

Point() = default;

Point(int x, int y) : _x(x), _y(y) {}

void *operator new(size_t) = delete;

void operator delete(void *) = delete;

void *operator new[](size_t) = delete;

void operator delete[](void *) = delete;
};

3.2 限制类对象在堆上分配

如果想一个类的使用者只能通过new运算符或new [ ]运算符在堆上开辟空间,可以将类的析构函数显式删除,但通过new运算符或new [ ]运算符创建的对象也不能调用delete delete[]销毁。

1
2
3
4
5
6
7
8
9
10
11
12
class Point
{
public:
int _x;
int _y;

Point() = default;

~Point() = delete;

Point(int x, int y) : _x(x), _y(y) {}
};