C++ explicit 关键字
C++ 中的 explicit 关键字用于禁止构造函数和转换运算符的隐式转换。本文介绍它解决的问题、具体用法以及最佳实践。
1. 隐式转换的问题
当一个类拥有单参数构造函数时,编译器可以自动将参数类型隐式转换为该类类型。这在某些场景下会带来意想不到的行为:
1 | class String { |
看起来很方便,但考虑这个例子:
1 | class Buffer { |
100 被隐式转换成了一个 Buffer 对象,这种转换很可能不是程序员的本意,且难以排查。
2. explicit 构造函数
在构造函数前加 explicit,即可禁止隐式转换:
1 | class Buffer { |
对比加与不加 explicit 的行为:
1 | class A { |
3. explicit 与拷贝初始化
explicit 构造函数不能用于拷贝初始化(= 语法),但可以直接初始化(() 或 {} 语法):
1 | class Foo { |
注意:Foo f4 = Foo(42) 之所以合法,是因为右侧已经显式构造了一个 Foo 临时对象,这里发生的是同类对象之间的拷贝初始化,而非从 int 的隐式转换。
函数传参和返回值同样受影响:
1 | void func(Foo f) {} |
4. explicit 转换运算符(C++11)
C++11 起,explicit 也可用于转换运算符。最常见的用途是 explicit operator bool():
1 | class SmartPtr { |
加上 explicit 后:
1 | class SmartPtr { |
C++ 标准规定,explicit operator bool() 在以下上下文中可以当作隐式转换使用(称为”上下文转换为 bool”):
if、while、for的条件部分!、&&、||的操作数- 三元运算符
?:的条件部分
这就是标准库智能指针(std::unique_ptr、std::shared_ptr)的实现方式。
5. C++20 的改进
C++20 允许 explicit 接受一个布尔常量表达式参数,实现条件性 explicit:
1 | template<typename T> |
这在模板编程中非常实用,可以根据类型特征决定是否允许隐式转换,而不需要写两个构造函数。
6. 最佳实践
- **单参数构造函数几乎总是应该加
explicit**,除非你明确需要隐式转换(如String(const char*)这种语义上自然等价的转换) - **转换运算符优先使用
explicit operator bool()**,避免 bool 继续隐式转换为整数 - **多参数构造函数也可以加
explicit**,C++11 起生效(禁止从花括号列表的隐式转换) - **不要为了方便而省略
explicit**,隐式转换带来的 bug 往往难以定位
1 | class Point { |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 iehtian!
