【C/C++】为什么要noexcept
为什么要noexcept
在C++中,noexcept
修饰符用于指示函数不会抛出异常
1. 性能优化
- 减少异常处理开销:编译器在生成代码时,若函数标记为
noexcept
,可以省略异常处理的相关机制(如栈展开代码),从而减少生成代码的体积并提升运行效率。 - 移动语义优化:标准库容器(如
std::vector
)在重新分配内存时,若元素的移动操作(如移动构造函数)被标记为noexcept
,则优先使用移动而非拷贝。例如:
此时,class MyClass { public:MyClass(MyClass&& other) noexcept { /* ... */ } // 移动构造函数标记为noexcept };
std::vector<MyClass>
在扩容时会高效地移动元素而非拷贝。
2. 标准库行为控制
- 容器操作的异常安全:标准库的某些操作(如
std::vector::push_back
)会根据类型是否支持noexcept
移动来决定使用移动还是拷贝。若移动操作可能抛出异常(未标记noexcept
),为保障异常安全,标准库会回退到拷贝操作。
3. 接口明确性
- 契约式设计:
noexcept
作为函数签名的一部分,明确告知调用者该函数不会抛出异常,增强代码可读性和可靠性。例如:void safe_operation() noexcept; // 明确承诺不抛异常
4. 错误处理约束
- 强制终止异常传播:若
noexcept
函数内部抛出异常,程序会直接调用std::terminate()
终止,避免异常传播导致未定义行为。例如:
开发者需确保void risky() noexcept {throw std::runtime_error("oops"); // 触发程序终止 }
noexcept
函数确实不会抛出异常。
5. 虚函数与继承
- 异常规范一致性:派生类重写的虚函数必须与基类的异常说明兼容。若基类虚函数为
noexcept
,派生类版本也需标记noexcept
:class Base { public:virtual void func() noexcept {} }; class Derived : public Base { public:void func() noexcept override {} // 必须同样标记noexcept };
6. 条件性noexcept
- 动态异常说明:通过
noexcept(condition)
根据编译期条件决定是否禁止异常:template<typename T> void swap(T& a, T& b) noexcept(noexcept(T(std::move(a))) && noexcept(a.operator=(std::move(b)))) {// 当T的移动构造和移动赋值为noexcept时,swap才为noexcept }
何时使用noexcept
- 移动构造函数/赋值运算符(标准库优化的关键)。
- 简单函数(如
getter
、资源释放函数)。 - 标准库要求或可显著提升性能的场景。
注意事项
- 谨慎使用:错误标记
noexcept
可能导致程序意外终止。 - 析构函数:默认隐式
noexcept
,若需允许析构函数抛出异常,需显式标记noexcept(false)
(但通常不推荐)。
总结:noexcept
通过指导编译器和标准库优化,提升程序性能与可靠性,但需在充分确保函数无异常抛出的前提下使用。