0%

C++11之delete

引言:delete关键字可以禁用某些函数,或者不期望转换和操作符。

1.背景

对于 C++ 的类,如果程序员没有为其定义特殊成员函数,那么在需要用到某个特殊成员函数的时候,编译器会隐式的自动生成一个默认的特殊成员函数,比如拷贝构造函数,或者拷贝赋值操作符。例如:

1
2
3
4
5
6
7
8
9
10
11
class X {
public:
X();
};

int main() {
X x1;
X x2 = x1; // 正确,调用编译器隐式生成的默认拷贝构造函数
X x3;
x3 = x1; // 正确,调用编译器隐式生成的默认拷贝赋值操作符
}

在上述程序中,程序员不需要自己手动编写拷贝构造函数以及拷贝赋值操作符,依靠编译器自动生成的默认拷贝构造函数以及拷贝赋值操作符就可以实现类对象的拷贝和赋值。这在某些情况下是非常方便省事的,但是在某些情况下,假设我们不允许发生类对象之间的拷贝和赋值,可是又无法阻止编译器隐式自动生成默认的拷贝构造函数以及拷贝赋值操作符,那这就成为一个问题了。

2.delete的提出

为了能够让程序员显式的禁用某个函数,C++11标准引入了一个新特性:delete函数。程序员只需在函数声明后加上=delete,就可将该函数禁用。例如,我们可以将类 X 的拷贝构造函数以及拷贝赋值操作符声明为 delete函数,就可以禁止类 X 对象之间的拷贝和赋值。

1
2
3
4
5
6
7
8
9
10
11
12
13
class X {
public:
X();
X(const X&) = delete; // 声明拷贝构造函数为delete函数
X& operator = (const X&) = delete; // 声明拷贝赋值操作符为delete函数
};

int main() {
X x1;
X x2=x1; // 错误,拷贝构造函数被禁用
X x3;
x3=x1; // 错误,拷贝赋值操作符被禁用
}

在上述程序中,虽然只显式的禁用了一个拷贝构造函数和一个拷贝赋值操作符,但是由于编译器检测到类 X 存在用户自定义的拷贝构造函数和拷贝赋值操作符的声明,所以不会再隐式的生成其它参数类型的拷贝构造函数或拷贝赋值操作符,也就相当于类 X 没有任何拷贝构造函数和拷贝赋值操作符,所以对象间的拷贝和赋值被完全禁止了。

3.delete的用法及示例

delete函数特性还可用于禁用类的某些转换构造函数,从而避免不期望的类型转换。在下面程序中,假设类 X 只支持参数为双精度浮点数double类型的转换构造函数,而不支持参数为整数int类型的转换构造函数,则可以将参数为int类型的转换构造函数声明为delete函数。

1
2
3
4
5
6
7
8
9
10
class X {
public:
X(double);
X(int) = delete;
};

int main() {
X x1(1.2);
X x2(2); // 错误,参数为整数int类型的转换构造函数被禁用
}

delete函数特性还可以用来禁用某些用户自定义的类的new操作符,从而避免在自由存储区创建类的对象。例如:

1
2
3
4
5
6
7
8
9
10
class X {
public:
void *operator new(size_t) = delete;
void *operator new[](size_t) = delete;
};

int main() {
X *pa = new X; // 错误,new操作符被禁用
X *pb = new X[10]; // 错误,new[]操作符被禁用
}

必须在函数第一次声明的时候将其声明为delete函数,否则编译器会报错。即对于类的成员函数而言,delete函数必须在类体里(inline)定义,而不能在类体外(out-of-line)定义。例如:

1
2
3
4
5
6
class X {
public:
X(const X&);
};

X::X(const X&) = delete; // 错误,delete函数必须在函数第一次声明处声明

虽然 default函数特性规定了只有类的特殊成员函数才能被声明为default函数,但是delete函数特性并没有此限制。非类的成员函数,即普通函数也可以被声明为 delete函数。例如:

1
2
3
4
5
6
int add(int,int) = delete;

int main() {
int a, b;
add(a, b); // 错误,函数add(int, int)被禁用
}

值得一提的是,在上述程序中,虽然 add(int, int)函数被禁用了,但是禁用的仅是函数的定义,即该函数不能被调用。但是函数标示符add仍是有效的,在名字查找和函数重载解析时仍会查找到该函数标示符。如果编译器在解析重载函数时,解析结果为已经delete的函数,则会出现编译错误。例如:

1
2
3
4
5
6
7
8
9
int add(int, int) = delete;
double add(double a, double b) {
return a+b;
}

int main() {
cout << add(1, 3) << endl; // 错误,调用delete函数add(int, int)
cout << add(1.2, 1.3) << endl;
}

4.总结

1.delete函数用于禁用某个函数;
2.delete必须在函数第一次声明的时候使用,对于类的成员函数,即只能在类体内使用,这点和default不同;
3.delete既可以用于类的特殊成员函数,也可用于普通函数;
4.delete可以用来禁用某些用户自定义的类的new操作符,从而避免在自由存储区创建类的对象;
5.delete可以通过参数禁用某种参数类型的函数,并不会禁用函数的标识符,所以不会影响函数重载;