C++ 中 `explicit` 关键字详解:彻底理解隐式类型转换

在 C++ 中,explicit 是一个非常常见的关键字。很多初学者知道它的作用是:

explicit 可以阻止隐式类型转换。

但如果进一步追问:

  • 什么叫隐式类型转换?
  • explicit 到底阻止了什么?
  • 为什么构造函数可以使用 explicit
  • 类型转换函数为什么也可以使用 explicit
  • explicit 拷贝构造函数又是怎么回事?

这些问题就很容易混在一起。

本文从最基础的概念开始,系统梳理 explicit 的常见使用场景。


一、explicit 构造函数

这是 explicit 最常见的使用场景

先看一个没有 explicit 的例子:

cpp 复制代码
class A {
public:
    A(int x) {}
};

A a = 10;

这里看起来是在用 10 初始化 a,但实际上发生了一个隐式类型转换。

10 的类型是 int,而 a 的类型是 A,他们类类型不同,本不应该可以转换,但由于类 A 存在:

cpp 复制代码
A(int x)

编译器发现可以利用这个构造函数把 int 转换成 A

可以把它理解成:

cpp 复制代码
A a = A(10);

也就是说:

text 复制代码
int
 ↓
A(int)
 ↓
A

这个过程是由编译器自动完成的,因此叫做隐式类型转换。当然,编译器做了优化之后,就只接变成调用一次构造函数了,而不是上面说的先构造再复制


使用 explicit

如果我们不希望编译器自动进行这种转换,可以在构造函数前加上 explicit

cpp 复制代码
class A {
public:
    explicit A(int x) {}
};

此时:

cpp 复制代码
A a = 10;   // ❌ 编译错误

因为 explicit 告诉编译器:

不要偷偷使用这个构造函数进行隐式类型转换。

但是,如果我们明确写出构造过程:

cpp 复制代码
A a(10);    // ✅

就是允许的。

所以:

cpp 复制代码
class A {
public:
    explicit A(int x) {}
};

可以理解成:

text 复制代码
A a(10);    // ✅ 明确告诉编译器:我要构造 A

A a = 10;   // ❌ 不允许编译器偷偷把 int 转成 A

因此:

explicit 构造函数并不是禁止类型转换,而是禁止编译器隐式地进行类型转换。


二、explicit 构造函数还可以阻止函数参数的隐式转换

explicit 的作用不仅体现在:

cpp 复制代码
A a = 10;

还体现在函数参数中。

例如:

cpp 复制代码
class A {
public:
    A(int x) {}
};

void func(A a) {}

此时:

cpp 复制代码
func(10);

也是合法的。

为什么?

因为函数需要一个 A 类型的参数,而我们传入的是 int 类型的 10

编译器可以自动调用:

cpp 复制代码
A(int)

于是整个过程相当于:

cpp 复制代码
func(A(10));

也就是说:

text 复制代码
10
 ↓
A(10)
 ↓
func()

这里同样发生了隐式类型转换。


加上 explicit

如果修改成:

cpp 复制代码
class A {
public:
    explicit A(int x) {}
};

void func(A a) {}

那么:

cpp 复制代码
func(10);      // ❌

因为 A(int)explicit 构造函数,编译器不能偷偷把:

cpp 复制代码
10

转换成:

cpp 复制代码
A(10)

但是:

cpp 复制代码
func(A(10));   // ✅

是可以的。

因为这里是我们主动构造了 A

因此:

text 复制代码
func(10)
    ↓
需要 A
    ↓
是否允许 int → A 的隐式转换?
    ↓
explicit
    ↓
不允许

而:

cpp 复制代码
func(A(10));

则是:

text 复制代码
明确构造 A
    ↓
允许

三、explicit 构造函数到底禁止什么?

假设:

cpp 复制代码
class A {
public:
    explicit A(int);
};

那么最重要的就是区分下面几种情况。

情况 1:直接初始化

cpp 复制代码
A a(10);

允许。

因为这是我们明确告诉编译器:

请使用 10 构造一个 A


情况 2:拷贝初始化

cpp 复制代码
A a = 10;

不允许。

因为这种写法需要把 10 隐式转换成 A

explicit 禁止了这种隐式转换。


情况 3:函数参数

cpp 复制代码
void func(A a);

func(10);

不允许。

因为这里同样需要:

text 复制代码
int → A

的隐式转换。


情况 4:明确构造

cpp 复制代码
func(A(10));

允许。

因为我们明确告诉编译器:

text 复制代码
我要构造一个 A

所以第一大类可以总结成:

explicit 构造函数的主要作用,就是防止编译器偷偷使用该构造函数完成隐式类型转换。


四、explicit 类型转换函数

除了构造函数之外,C++ 中还有一种函数也可以进行类型转换:

类型转换函数(conversion function)

例如:

cpp 复制代码
class A {
public:
    operator int() {
        return 10;
    }
};

这个函数的作用是:

text 复制代码
A → int

例如:

cpp 复制代码
A a;

int x = a;

编译器会自动调用:

cpp 复制代码
a.operator int();

因此:

text 复制代码
A
 ↓
operator int()
 ↓
int

这里同样发生了隐式类型转换。


使用 explicit

如果不希望这种转换自动发生,可以写:

cpp 复制代码
class A {
public:
    explicit operator int() {
        return 10;
    }
};

此时:

cpp 复制代码
A a;

int x = a;   // ❌

编译器不能偷偷把 A 转换成 int

但是:

cpp 复制代码
int x = static_cast<int>(a);   // ✅

可以。

因为这里我们明确告诉编译器:

我要把 a 转换成 int

所以:

text 复制代码
int x = a;

属于隐式转换,被 explicit 阻止。

而:

cpp 复制代码
int x = static_cast<int>(a);

属于显式转换,因此可以正常进行。


五、explicit operator bool() 的特殊应用

在实际 C++ 编程中,我们经常会看到:

cpp 复制代码
explicit operator bool() const;

这是 explicit 类型转换函数中非常经典的一个应用。

例如:

cpp 复制代码
class A {
public:
    explicit operator bool() const {
        return true;
    }
};

我们可以这样使用:

cpp 复制代码
A a;

if (a) {
    // ...
}

这是允许的。

但是:

cpp 复制代码
bool x = a;

通常是不允许的。

为什么?

因为:

cpp 复制代码
if (a)

这种场景本身就是在进行一个明确的条件判断。

而我们通常希望一个对象:

可以被用于 if 判断,但不要让它随随便便转换成 bool 后参与其他隐式转换。

例如一个表示资源是否有效的类:

cpp 复制代码
class MyPtr {
public:
    explicit operator bool() const {
        return ptr != nullptr;
    }

private:
    int* ptr = nullptr;
};

于是:

cpp 复制代码
MyPtr p;

if (p) {
    // p 有效
}

非常自然。

但是我们不希望:

cpp 复制代码
int x = p;

这种奇怪的隐式转换发生。

所以:

cpp 复制代码
explicit operator bool()

在很多资源管理类、容器类、智能指针类中都非常有用。

你以后看到:

cpp 复制代码
explicit operator bool() const

可以直接理解成:

这个对象可以用于条件判断,但不希望它随意隐式转换成 bool


六、拷贝构造函数可以使用 explicit 吗?

答案是:

可以。

例如:

cpp 复制代码
class A {
public:
    explicit A(const A& other) = default;
};

这里:

cpp 复制代码
A(const A&)

仍然是一个拷贝构造函数。

explicit 并不会让它变成其他类型的函数。


那么它有什么作用?

关键在于区分:

直接初始化拷贝初始化

例如:

cpp 复制代码
A a;

A b(a);

这里是直接初始化。

因此可以使用 explicit 拷贝构造函数。

但是:

cpp 复制代码
A c = a;

这是拷贝初始化。

此时 explicit 拷贝构造函数不能用于这种初始化。

所以:

cpp 复制代码
class A {
public:
    explicit A(const A&) = default;
};

情况下:

cpp 复制代码
A b(a);      // ✅
A c = a;     // ❌

注意:explicit 并不是禁止复制

这是这里最容易产生误解的地方。

例如:

cpp 复制代码
explicit A(const A&);

并不意味着:

"A 不能复制。"

它真正表达的是:

这个拷贝构造函数不能用于隐式的拷贝初始化。

所以:

cpp 复制代码
A b(a);

仍然可以复制。

只是:

cpp 复制代码
A c = a;

这种初始化方式不再允许使用这个 explicit 拷贝构造函数。


七、总结:explicit 到底在干什么?

到这里可以把 explicit 的作用统一起来。

它并不是:

"禁止类型转换。"

而是:

"禁止编译器自动、隐式地使用这个函数进行类型转换或初始化。"

主要有两种常见使用场景。


1. explicit 构造函数

例如:

cpp 复制代码
class A {
public:
    explicit A(int);
};

阻止:

cpp 复制代码
A a = 10;      // ❌
func(10);      // ❌,如果 func 参数是 A

允许:

cpp 复制代码
A a(10);       // ✅
func(A(10));   // ✅

核心:

text 复制代码
int → A

不允许隐式转换,允许显式构造。


2. explicit 类型转换函数

例如:

cpp 复制代码
class A {
public:
    explicit operator int() const;
};

阻止:

cpp 复制代码
int x = a;     // ❌

允许:

cpp 复制代码
int x = static_cast<int>(a);  // ✅

核心:

text 复制代码
A → int

不允许隐式转换,允许显式转换。


3. explicit 拷贝构造函数

例如:

cpp 复制代码
class A {
public:
    explicit A(const A&) = default;
};

允许:

cpp 复制代码
A b(a);        // ✅

不允许:

cpp 复制代码
A c = a;       // ❌

它控制的是:

拷贝构造函数能不能参与拷贝初始化。

不过实际开发中,给普通拷贝构造函数加 explicit 并不常见。


最后记住一句话

如果以后面试或者学习 C++ 时被问:

explicit 的作用是什么?

最准确的回答是:

explicit 用于防止构造函数或类型转换函数被用于隐式类型转换,要求程序员明确写出构造或转换操作。

最典型的代码就是:

cpp 复制代码
class A {
public:
    explicit A(int x);          // int → A:禁止隐式转换
    explicit operator int();    // A → int:禁止隐式转换
};

所以看到 explicit,第一反应就是:

"这里作者不希望编译器偷偷帮我做类型转换。"

相关推荐
蒸蒸yyyyzwd1 小时前
cpp 选手秋招学习笔记 day34
c++·八股
Tairitsu_H1 小时前
[C++] C++11类和STL的新特性
开发语言·c++·stl·c++11
Lucis__2 小时前
基于责任链模式的消息队列—异步处理流水线的最佳实践
linux·c++·消息队列·责任链模式·ipc
Jackson_GJH2 小时前
C++虚函数、虚继承、虚基类
c++·c++基础
码匠许师傅3 小时前
【C++三方组件】开篇总论:为什么需要以及如何选型?
开发语言·c++
h_a_o777oah3 小时前
【Games101】C++ 软光追:光线追踪的求交逻辑和 BVH 提效实现及代码实现细节
c++·计算机图形学·光线追踪·games101·bvh·求交算法·软件渲染
cvby3 小时前
C++11--右值引用和移动语义
开发语言·c++
xiangyun613 小时前
【408数据结构 03】线性表与顺序表:C++手写SeqList
开发语言·数据结构·c++
xxxiugou1234 小时前
双指针解题秘籍:从入门到精通
c语言·数据结构·c++·算法