跳转到内容
新建笔记

C++ explicit:构造转换、初始化与布尔条件

explicit 控制某个构造函数或转换函数能否参与隐式转换。它不取消构造能力,也不表示这个类从此不能复制。读调用处时,要同时看选中了哪个函数,以及采用哪一种初始化语法。

本篇以 C++17 为准,保留 MyClass 与数值 42 的原场景,并把成功程序和预期编译错误分开。

构造函数:显式写出转换意图

跳转到“构造函数:显式写出转换意图”

假设声明为 explicit MyClass(int),以下结果成立:

写法结果原因
MyClass a(42);可以直接初始化
MyClass b{42};可以直接列表初始化
MyClass c = 42;不可以拷贝初始化不能用这个显式转换构造函数
MyClass d = {42};不可以拷贝列表初始化选中显式构造函数时不合法
printMyClass(42);不可以实参需要隐式转换为 MyClass
printMyClass(MyClass{42});可以实参中已明确构造了对象
MyClass e = a;通常可以这里使用可用的拷贝构造函数,不是 MyClass(int)

非 explicit 的单参数构造函数、或其他参数都有默认值的构造函数,常产生自动转换。explicit 也可以用于多参数构造函数和默认构造函数,并非只允许放在单参数构造函数前。多参数形式同样会影响拷贝列表初始化。C++17 转换构造函数

“拷贝初始化”是语法和语义分类,不代表一定真的调用拷贝构造函数,也不只发生在等号旁。例如按值传参和返回值也涉及相应的初始化规则。

转换函数:从类对象转到别的类型

跳转到“转换函数:从类对象转到别的类型”

explicit operator int() const 允许 static_cast<int>(object) 等显式用法,但不能让 printInt(object) 或 int n = object; 自动成功。转换函数不另写返回类型;目标类型已经在 operator int 中。C++17 转换函数

explicit operator bool() 有一个常用边界:if (object)、while (object) 和逻辑运算需要的布尔条件可以使用它;普通的 bool b = object; 仍被拒绝。布尔条件上下文有自己的转换规则,不能把这个结论推广成显式 operator int() 也能隐式传给整数参数。条件中的布尔转换

完整程序:自动转换、明确构造与条件判断

跳转到“完整程序:自动转换、明确构造与条件判断”
#include <cassert>
#include <ostream>
#include <sstream>
#include <string>
#include <type_traits>
class ImplicitValue {
public:
ImplicitValue(int value) : value_(value) {}
int value() const { return value_; }
private:
int value_;
};
class MyClass {
public:
explicit MyClass(int value) : value_(value) {}
void print(std::ostream& out) const { out << value_; }
explicit operator int() const { return value_; }
private:
int value_;
};
class Ready {
public:
explicit Ready(bool value) : value_(value) {}
explicit operator bool() const { return value_; }
private:
bool value_;
};
int readImplicit(const ImplicitValue& value) { return value.value(); }
void printMyClass(const MyClass& object, std::ostream& out) { object.print(out); }
void printInt(int value, std::ostream& out) { out << value; }
int main() {
static_assert(std::is_convertible_v<int, ImplicitValue>);
static_assert(!std::is_convertible_v<int, MyClass>);
static_assert(std::is_constructible_v<MyClass, int>);
static_assert(std::is_copy_constructible_v<MyClass>);
static_assert(!std::is_convertible_v<MyClass, int>);
static_assert(!std::is_convertible_v<Ready, bool>);
assert(readImplicit(42) == 42);
MyClass first(42);
MyClass second{42};
MyClass copied = first;
assert(static_cast<int>(second) == 42);
assert(static_cast<int>(copied) == 42);
std::ostringstream out;
printMyClass(MyClass{42}, out);
out << ' ';
printInt(static_cast<int>(first), out);
assert(out.str() == "42 42");
const Ready ready{true};
bool entered = false;
if (ready) entered = true;
assert(entered && ready && !Ready{false});
const bool direct(ready);
assert(direct);
}

在上面的类定义下,单独尝试 MyClass bad = 42;、MyClass bad = {42};、printMyClass(42, out);、printInt(first, out); 和 bool bad = ready;,都应产生编译错误。它们是接口约束的验证,不应直接混入声称可以运行的程序中。

设计接口时,先决定某种转换是否应该自动发生。例如“整数是计数、容量还是设备编号”可能需要读者明确表达;已有自然值转换语义的类型则可有意识地允许转换。explicit 提供这个选择,本身不验证数值范围或业务正确性。