C++ explicit&noexcept关键字
C++ explicit&noexcept关键字
explicit关键字
在 C++ 中,explicit 关键字用于避免编译器在特定情况下进行隐式类型转换。它主要作用于构造函数和转换函数,防止不必要或意外的类型转换发生,从而提高代码的安全性和可读性。
1. 作用于构造函数
当一个构造函数只接受一个参数时,它通常会被编译器视为可以进行隐式类型转换。例如,如果你定义一个构造函数允许通过单个参数初始化对象,编译器可能会将该参数自动转换为对象类型。这种隐式转换有时可能会导致难以调试的错误。
explicit 关键字可以禁止这种隐式转换,使得构造函数只能用于显式地创建对象。
示例(没有 explicit):
class MyClass {
public:
MyClass(int x) { }
};
int main() {
MyClass obj = 10; // 隐式调用 MyClass(10)
}
在这个例子中,MyClass 的构造函数可以隐式地将 10 转换为 MyClass 类型的对象。
使用 explicit 禁止隐式转换:
class MyClass {
public:
explicit MyClass(int x) { }
};
int main() {
// MyClass obj = 10; // 错误:不能隐式转换
MyClass obj(10); // 正确:必须显式调用构造函数
}
这里,通过添加 explicit 关键字,编译器会拒绝隐式转换,要求必须显式调用构造函数。
2. 作用于转换函数
转换函数(即 operator 函数)可以将一个类的对象转换为其他类型。在某些情况下,自动的类型转换可能会带来意想不到的结果,因此 explicit 也可以用于修饰转换函数,以禁止隐式转换。
示例(没有 explicit):
class MyClass {
public:
operator int() const { return 42; }
};
int main() {
MyClass obj;
int x = obj; // 隐式调用转换函数,将 obj 转换为 int 类型
}
使用 explicit 禁止隐式转换:
class MyClass {
public:
explicit operator int() const { return 42; }
};
int main() {
MyClass obj;
// int x = obj; // 错误:不能隐式转换
int x = static_cast<int>(obj); // 正确:必须显式转换
}
这里,explicit 禁止了隐式转换,需要使用 static_cast 进行显式转换。
总结:
- 构造函数:
explicit用于防止通过单参数构造函数的隐式类型转换,要求显式创建对象。 - 转换函数:
explicit用于禁止类对象到其他类型的隐式转换,要求显式调用转换函数。
noexcept关键字
在 C++ 中,noexcept 是一个关键字,用于指定一个函数是否承诺不抛出异常。它的主要作用是告诉编译器和程序员,这个函数在正常情况下不会抛出异常,从而可以进行某些优化和错误处理。
noexcept 的作用:
-
声明函数不会抛出异常:
当一个函数被标记为noexcept,它承诺不会抛出异常。如果该函数在运行时确实抛出了异常,程序会直接调用std::terminate()并中止执行,而不会像正常的异常处理流程那样进行栈展开。例如:
void foo() noexcept { // 函数体不会抛出异常 } -
优化编译器行为:
标记为noexcept的函数可以让编译器进行更好的优化,特别是在那些涉及异常处理的代码路径上。编译器知道不需要生成栈展开(stack unwinding)代码来处理异常,因为noexcept承诺不会抛出异常。 -
与异常安全性有关:
在异常安全的代码中,noexcept有助于编写更具鲁棒性(健壮性)的代码。某些标准库的操作(例如容器的某些操作)在处理涉及noexcept的函数时,行为可能会有所不同。例如,std::vector的移动操作要求其元素的移动构造函数是noexcept的,否则在扩展容量时会回退到复制操作。 -
条件性的
noexcept:
noexcept可以是条件性的,即你可以根据某些条件决定函数是否是noexcept。这种情况主要用于模板代码,确保模板实例化时只有在某些条件下才标记为noexcept。例如:
template <typename T> void foo(T&& t) noexcept(noexcept(T())) { // 根据 T 的构造函数是否抛出异常来决定是否是 noexcept }
使用场景:
-
移动构造函数和移动赋值运算符:
如果一个类的移动构造函数或移动赋值运算符被标记为noexcept,那么标准库容器在移动该对象时会更高效。例如:class MyClass { public: MyClass(MyClass&&) noexcept = default; MyClass& operator=(MyClass&&) noexcept = default; }; -
析构函数:
通常,析构函数默认是noexcept。因为在异常传播时,如果析构函数抛出异常,C++ 会调用std::terminate()。例如:
class Foo { public: ~Foo() noexcept { // 不应抛出异常 } };
noexcept 和 throw() 的区别:
noexcept 是 C++11 引入的,取代了早期的 throw() 异常规范。throw() 的语义是标记函数不会抛出任何类型的异常,但它的行为和兼容性存在问题,因此被 noexcept 取代。
示例:
void func() noexcept {
// 保证不抛出异常
}
void test() {
try {
func();
} catch (...) {
// 永远不会捕获到异常,因为 func 是 noexcept
}
}
总结来说,noexcept 用来声明和约束函数不抛出异常,有助于提高程序的安全性和性能。

浙公网安备 33010602011771号