绝大多数单参数构造函数应加explicit;例外需有明确理由。只要构造函数能被隐式调用(如单实参或带默认值的多参),就应加explicit,包括C++11起的显式转换运算符,以防止意外隐式转换引发的隐蔽错误。

绝大多数单参数构造函数,都应该加 explicit;不加的例外必须有明确、可辩护的理由,否则就是留坑。
什么时候必须加 explicit
只要构造函数能被编译器用作隐式转换(即:仅靠一个实参就能调用),就默认应该加 explicit。典型场景包括:
- 类接受一个
int、const char*、double等基础类型作为唯一实参 - 构造函数有多个参数,但除第一个外其余都有默认值(例如
MyClass(int x, bool flag = true)) - C++11 起,自定义类型转换运算符(如
explicit operator bool() const)也适用
不加 explicit 的后果不是“不能编译”,而是“编译通过但行为诡异”:比如 void log(const Logger&) 被意外调用为 log(42),背后悄悄构造了一个临时 Logger 对象——而这个对象可能开日志文件、占内存、甚至触发网络请求。
explicit 会阻止哪些写法
加了 explicit 后,以下三种常见写法全部失效:
- 拷贝初始化:
MyClass obj = 42;→ 编译错误 - 函数参数隐式转换:
void f(MyClass); f(42);→ 编译错误 - 返回值隐式转换:
MyClass make(); return 100;→ 编译错误(若函数声明返回MyClass)
但这些写法依然合法:
- 直接初始化:
MyClass obj(42); - 显式构造传参:
f(MyClass(42)); - 显式转换:
f(static_cast<myclass>(42));</myclass>
不加 explicit 的少数合理场景
不是所有单参构造函数都该加 explicit;关键看“这个转换是否天然、无歧义、且符合直觉”。例如:
-
std::string s = "hello";——std::string(const char*)不是explicit,因为从字符串字面量构造字符串是语义核心,不是“副作用转换” - 数值包装类如
struct Meter { explicit Meter(double v) : val(v) {} double val; };—— 这里加explicit是对的,因为Meter m = 1.5;容易让人误以为单位无关,实际应强制写成Meter m(1.5);表明是米制 - 某些 DSL 类型(如正则表达式类
Regex r = "a+b";)可能选择不加,前提是团队共识且文档明确
注意:explicit 和性能无关,它只影响编译期检查;加了不会导致额外运行时开销。
C++11 后扩展:explicit 也能修饰转换运算符
以前只能防构造函数的隐式转换,C++11 开始还能防反向转换。比如:
class NonNullPtr {
T* ptr_;
public:
explicit operator bool() const { return ptr_ != nullptr; }
};
这样写之后,if (ptr) { ... } 仍合法(条件上下文允许显式 operator bool),但 bool b = ptr; 或 return ptr;(函数返回 bool)就会编译失败——避免把指针“悄悄变成布尔值”再参与算术运算等危险操作。
最容易被忽略的是:explicit 对“多参数但带默认值”的构造函数同样生效,而且这种构造函数反而更危险——表面看是“配置类”,实际可能在任意函数调用点被悄悄构造出来,排查成本极高。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











