每周技巧 #143:C++11 删除函数(`= delete`)
本节阅读量:本文翻译自 Abseil 官网的 Tip of the Week #143: C++11 Deleted Functions (= delete)。
原文最初作为 TotW #143 发布于 2018 年 3 月 2 日。
更新于 2020 年 4 月 6 日。
快捷链接:abseil.io/tips/143
引言
一般意义上的接口通常定义可以调用的一组操作。但有时我们可能想表达相反的意思:显式定义一组不应该使用的操作。例如,禁用拷贝构造函数和拷贝赋值运算符,是限制特定类型拷贝语义的常见方式。
语言提供了多种选项来施加这类限制(我们很快会逐一探索):
- 提供一个只包含运行时检查的哑实现。
- 使用访问控制(protected/private)让函数不可访问。
- 声明函数,但有意省略定义。
- 从 C++11 开始:显式把函数定义为“已删除”。
C++11 之前的技术范围很广,从运行时检查(#1)到编译期(#2)或链接期(#3)诊断。虽然久经考验,但这些技术远非完美:对于约束是静态的多数情况,运行时检查并不理想;链接期检查又把诊断推迟到构建流程非常晚的阶段。此外,链接期诊断并不保证发生(缺少 ODR 使用函数的定义是一种 ODR 违规),而且实际诊断消息很少对开发者友好。
编译期检查更好,但仍有缺陷。它只适用于成员函数,并且基于访问性约束,这既啰嗦又容易出错,还容易有漏洞。此外,引用这类函数产生的错误可能具有误导性,因为错误说的是访问限制,而不是接口误用。
用 #2 和 #3 禁用拷贝会像这样:
|
|
每个类都手动这样写很快会令人厌烦,所以开发者通常会用下面某种方式打包它们:
“mixin”方式(boost::noncopyable、non-copyable mixin)
|
|
宏方式
|
|
C++11 删除定义
C++11 通过一个新的语言特性解决了对更好方案的需求:删除定义 [dcl.fct.def.delete]。(见 C++ 标准草案中的“deleted definitions”。)任何函数都可以被显式定义为已删除:
|
|
这个语法很直接,类似默认化函数,但有几个值得注意的差异:
- 任何函数都可以删除,包括非成员函数(与
=default相比,后者只适用于特殊成员函数)。 - 函数只能在第一次声明时删除(不同于
=default)。
需要牢记的关键点是,=delete 是函数定义(它不会移除或隐藏声明)。因此,被删除函数是已定义的,并且和任何其他函数一样参与名称查找和重载决议。它是一种特殊的*“放射性”定义,意思是“不要碰!”*。
尝试使用已删除函数会导致清晰诊断的编译期错误,这是它相对 C++11 前技术的关键优势之一。
|
|
|
|
注意:通过显式把拷贝操作定义为已删除,我们也抑制了移动操作(有用户声明的拷贝操作会阻止隐式声明移动操作)。如果意图是使用隐式移动操作定义一个只可移动类型,可以用 =default 把它们“带回来”,例如:
|
|
其他用法
虽然上面的例子集中在拷贝语义上(这可能是最常见的情况),但任何函数(成员或非成员)都可以删除。
由于已删除函数参与重载决议,它们可以帮助捕捉非预期用法。假设我们有下面这个重载的 print 函数:
|
|
调用 print('x') 会打印 'x' 的整数值,而开发者很可能想写的是 print("x")。我们可以捕捉这一点:
|
|
注意,=delete 不只影响函数调用。尝试取得已删除函数的地址也会导致编译错误:
|
|
这个例子提取自真实应用:absl::StrCat()。当接口的某个特定部分必须受限时,删除函数都很有价值。
把析构函数定义为已删除,比把它设为 private 更严格(虽然这是把大锤,可能引入超出预期的限制):
|
|
再举一个例子,这次我们只想允许分配非数组对象(真实世界示例):
|
|
总结
=delete 提供了一种显式方式,用来表达接口中不应被引用的部分,同时也比 C++11 前的惯用法提供更好的诊断。任何代码,包括编译器生成的代码,都不能引用已删除函数。对于更细致的访问控制,访问说明符或更复杂的技术(例如 技巧 #134 中讨论的 passkey idiom)更合适。
重要:由于删除定义是接口的一部分,它们应该和接口的其他部分具有相同访问说明符。具体来说,这意味着它们通常应该是 public。实践中,这也会产生最好的诊断(private 加 =delete 没有太大意义)。
致谢:本技巧包含许多人的关键贡献和反馈,特别感谢 Mark Mentovai、James Dennett、Bruce Dawson 和 Yitzhak Mandelbaum。
参考
- TotW #131:特殊成员函数和 =default
- TotW #134:make_unique 和私有构造函数
deleted functions- access specifiers
- C++ Core Guidelines: C.81
- Google C++ style guide: Copyable and Movable Types