C++中explicit关键字的含义与用法

C++中explicit关键字的含义与用法

技术背景

在C++里,编译器允许进行一次隐式转换来匹配函数的参数。也就是说,编译器可以利用带有单个参数的构造函数,将一种类型转换为另一种类型,以满足参数的类型要求。然而,这种隐式转换有时会导致意外的构造和潜在的错误。为了避免这类问题,C++引入了explicit关键字。

实现步骤

1. 了解隐式转换与转换构造函数

转换构造函数是指那些可以用于隐式转换的单参数构造函数。下面是一个示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
struct Foo {
// 单参数构造函数,可用于隐式转换
Foo(int x) {}
};
struct Faz {
// 也是转换构造函数
Faz(Foo foo) {}
};

void bar(Foo foo) {};

int main() {
// 转换构造函数允许传递一个int
bar(42);
// 同样由于转换构造函数而被允许
Foo foo = 42;
// 错误!这需要两次转换 (int -> Foo -> Faz)
Faz faz = 42;
}

2. 使用explicit关键字防止隐式转换

在构造函数前加上explicit关键字,可以阻止编译器使用该构造函数进行隐式转换。修改上述代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Foo {
// 显式构造函数,防止隐式转换
explicit Foo(int x) {}
};
struct Faz {
explicit Faz(Foo foo) {}
};

void bar(Foo foo) {};

int main() {
// 错误!没有隐式转换 int 到 Foo 可能
// bar(42);
// 正确:直接初始化,显式转换
bar(Foo(42));
// 错误!没有隐式转换 int 到 Foo 可能
// Foo foo = 42;
// 正确:直接初始化
Foo foo(42);
}

3. explicit在转换函数中的应用

从C++11开始,explicit关键字也可用于用户定义的类型转换运算符,以确保只有显式转换才是有效的。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class C{
public:
explicit inline operator bool() const {
return true;
}
};

int main() {
C c;
// 正确:显式转换
bool b = static_cast<bool>(c);
// 错误:隐式转换无效
// bool b = c;
return 0;
}

核心代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 示例类定义
struct X {
explicit X(int a); // X 可以从 int 显式构造
explicit operator Z (); // X 可以显式转换为 Z
};

struct Y{
Y(int a); // int 可以隐式转换为 Y
operator Z (); // Y 可以隐式转换为 Z
};

// 函数定义
void foo(X x) { }
void bar(Y y) { }
void baz(Z z) { }

// 函数调用示例
foo(2); // 错误:没有隐式转换 int 到 X 可能
foo(X(2)); // 正确:直接初始化,显式转换
foo(static_cast<X>(2)); // 正确:显式转换

bar(2); // 正确:通过 Y(int) 进行隐式转换
bar(Y(2)); // 正确:直接初始化
bar(static_cast<Y>(2)); // 正确:显式转换

最佳实践

  • 通常情况下,建议将单参数构造函数声明为explicit,除非实现明确禁止这样做。
  • 对于带有默认参数的构造函数,如果可以作为单参数构造函数使用,也应考虑将其声明为explicit
  • 在类接口中使用explicit关键字,能强制接口的使用者明确表达所需的转换。

常见问题

1. 为什么使用explicit转换函数或构造函数?

  • 避免歧义:转换构造函数和非显式转换函数可能会引入歧义。例如,当一个类型可以通过不同方式转换为目标类型时,编译器可能无法确定使用哪种转换方式。
  • 防止意外行为:它们可能导致意外的行为,例如在调用函数时传递一个值,却得到与预期不同的结果。

2. 构造函数有默认参数时,explicit关键字如何使用?

如果构造函数的所有参数都有默认值,或者从第二个参数开始有默认值,这些构造函数都可以作为单参数构造函数使用。因此,建议将它们声明为explicit,以防止意外的隐式转换。

3. 何时不应该使用explicit关键字?

当创建一个函数对象(functor)时,可能不希望将单参数构造函数声明为explicit。例如,add_x add30 = 30;这样的对象创建方式可能是合理的。


C++中explicit关键字的含义与用法
https://119291.xyz/posts/2025-05-09.cpp-explicit-keyword-meaning-and-usage/
作者
ww
发布于
2025年5月9日
许可协议