12 / 80 · C++11 · 约 8 分钟
explicit:把转换意图留在调用处
explicit 阻止构造函数或转换函数参与某些隐式转换,但仍允许直接初始化和显式转换。它适合保护单位、容量和资源包装类型;explicit operator bool 还能支持条件判断而避免意外数值转换。
为什么不总是自动转换
假设 Duration 只有一个接受 int 的构造函数,未加 explicit 时,接收 Duration 的函数可能接受裸整数,赋值或返回值语境也可能悄悄构造临时对象。这样虽然少写几个字符,却隐藏了整数的单位、范围和转换成本。
给构造函数加 explicit 后,调用者仍能写 Duration{3} 或 Duration(3),但不能直接把 3 当作 Duration 传参。它表达“允许构造,但要求在调用处说清楚”,不是禁止这个构造函数,也不是让对象不可复制。
区分初始化语境
T x(3) 和 T x{3} 是直接初始化,可以考虑 explicit 构造函数;T x = 3 的复制初始化不使用它。T x = {3} 是复制列表初始化,如果选中的构造函数是 explicit,则程序不合法。花括号本身不意味着所有 explicit 限制都消失。
explicit 也不仅用于一个参数的构造函数,多参数构造和带默认参数的构造同样可能需要约束列表转换。示例通过 is_constructible 与 is_convertible 区分“能明确构造”及“能隐式转换”,并给出实际使用的对象。
受控的布尔转换
explicit operator bool() 可以用于 if、while、逻辑运算等布尔语境,也可以通过 static_cast<bool> 显式调用;却不会像普通隐式整数转换一样轻易参与无关算术。资源包装类型因此能自然表示有效性,又避免被当作一个普通计数值。
C++20 的 explicit(条件) 允许泛型包装依据底层类型的可转换性决定是否显式,适合精细的库接口。日常业务类应先选择简单明确的策略:存在信息损失、单位变化或非显然成本时,通常要求调用者明确构造。
容易答错的地方
- explicit 不校验整数范围,也不防止构造函数体里的错误;业务不变量仍需单独检查。
- explicit operator bool 支持条件判断,但 bool b = object 的普通复制初始化仍不接受该显式转换。
运行一个例子
最低标准 C++11 · 完整程序 · 下载 .cpp
#include <cassert>
#include <iostream>
#include <type_traits>
class Count {
int value_;
public:
explicit Count(int value) : value_(value) {}
int value() const { return value_; }
explicit operator bool() const { return value_ != 0; }
};
int main() {
static_assert(std::is_constructible<Count, int>::value, "direct construction");
static_assert(!std::is_convertible<int, Count>::value, "no implicit conversion");
Count count{3};
const bool active = static_cast<bool>(count);
assert(active);
if (count) std::cout << count.value() << '\n';
}
在本地编译
g++ -std=c++11 -Wall -Wextra -Wpedantic -pthread basics-explicit.cpp -o example && ./example预期结果
3
CHECK YOUR UNDERSTANDING
合上答案,试着解释。
对 explicit Count(int),Count a(2)、Count b{2}、Count c = 2、Count d = {2} 哪些合法?若函数 accept(Count) 要传 2,该如何写?
查看参考答案
a 与 b 合法,分别是直接初始化与直接列表初始化。c 不能隐式调用 explicit 构造函数;d 的复制列表初始化选中 explicit 构造函数后不合法。调用应写 accept(Count{2}),在调用点明确执行构造,保留接口要求表达转换意图的目的。
继续查证
标准草案链接会随工作草案更新;本文版本标记对应示例最低要求,不表示草案中的所有新规则都适用于旧标准。