在 C++ 的异常处理体系中,异常说明(Exception Specification) 曾是一种用于声明函数可能抛出哪些异常的机制。从 C++98 引入到 C++17 弃用,异常说明经历了多次变革,其设计初衷与实际效果的差距引发了诸多争议。
在 C++98 中,异常说明是一种函数声明的语法,用于指定函数可能抛出的异常类型。其基本形式如下:
// C++98 异常说明语法
void func() throw(int, std::string); // 声明func可能抛出int或string类型的异常
void noexceptFunc() throw(); // 声明函数不抛出任何异常C++11 对异常说明进行了重大改革,引入了更简洁的noexcept说明符:
// C++11 异常说明语法
void mayThrow(); // 隐式允许抛出任何异常
void noThrow() noexcept; // 显式声明不抛出任何异常
void noThrowIf(bool b) noexcept(b); // 条件性不抛出异常C++ 异常说明主要分为三类:
①动态异常说明(Dynamic Exception Specification)
throw()语法std::unexpected()②noexcept 异常说明(Noexcept Specification)
noexcept关键字std::terminate()③弃用与保留
throw())noexcept成为推荐的异常说明方式当函数违反 C++98 风格的异常说明时,会触发以下行为:
std::unexpected()函数std::unexpected()会调用std::terminate()终止程序std::set_unexpected()自定义unexpected处理函数 #include <iostream>
#include <exception>
using namespace std;
// 声明函数只抛出int异常
void func() throw(int) {
throw string("Oops!"); // 违反异常说明
}
// 自定义unexpected处理函数
void myUnexpected() {
cout << "Caught unexpected exception!" << endl;
throw; // 尝试重新抛出异常
}
int main() {
set_unexpected(myUnexpected);
try {
func();
} catch (int e) {
cout << "Caught int: " << e << endl;
} catch (...) {
cout << "Caught other exception" << endl;
}
return 0;
}
noexcept说明符在编译时和运行时都有约束:
noexcept函数不会抛出异常,进行更激进的优化noexcept,直接调用std::terminate(),无法恢复 #include <iostream>
#include <stdexcept>
using namespace std;
// 声明函数不抛出异常
void func() noexcept {
throw runtime_error("Exception thrown!"); // 违反noexcept
}
int main() {
try {
func();
} catch (const exception& e) {
cout << "Caught exception: " << e.what() << endl; // 不会执行
}
return 0;
}
C++11 引入的noexcept是推荐的声明函数不抛出异常的方式:
// 无条件不抛出异常
void pureFunction() noexcept;
// 有条件不抛出异常(当参数为true时)
void conditionalNoThrow(bool condition) noexcept(condition);noexcept不仅是说明符,还是一个操作符,用于在编译时检查表达式是否可能抛出异常:
#include <iostream>
using namespace std;
void mayThrow() {}
void noThrow() noexcept {}
int main() {
cout << boolalpha;
cout << "mayThrow() is noexcept: " << noexcept(mayThrow()) << endl; // false
cout << "noThrow() is noexcept: " << noexcept(noThrow()) << endl; // true
cout << "1 + 2 is noexcept: " << noexcept(1 + 2) << endl; // true
return 0;
}
noexcept,以启用容器的优化noexcept,除非显式声明为noexcept(false)成员函数的异常说明遵循与普通函数相同的规则,但需注意:
class MyClass {
public:
// 普通成员函数的异常说明
void safeMethod() noexcept;
void riskyMethod() throw(int); // C++98风格,不推荐
// 静态成员函数的异常说明
static void staticMethod() noexcept;
// 构造函数的异常说明
MyClass() noexcept;
// 析构函数的异常说明(见下文)
~MyClass() override; // 默认noexcept
};异常说明是函数签名的一部分,意味着:
class Base {
public:
virtual void func() throw(int) {} // C++98风格
};
class Derived : public Base {
public:
// 错误:异常说明比基类更宽松
// void func() override {} // 编译错误
// 正确:异常说明必须与基类兼容或更严格
void func() throw(int, char) override {} // 允许
};C++11 起,析构函数默认隐式noexcept,除非显式声明为noexcept(false):
class Resource {
public:
~Resource() {
// 默认noexcept,即使未显式声明
}
};
class RiskyResource {
public:
~RiskyResource() noexcept(false) {
// 可能抛出异常
}
};析构函数抛出异常可能导致程序终止,因为:
std::terminate() class Logger {
public:
~Logger() {
throw runtime_error("Logging failed!"); // 危险:析构函数抛出异常
}
};
void func() {
Logger logger;
throw logic_error("Oops!"); // 触发栈展开,调用logger析构函数
} // 程序在此处终止try-catch块捕获析构函数中的异常并处理close()或release()方法让用户主动释放资源 class DatabaseConnection {
public:
~DatabaseConnection() noexcept {
try {
close(); // 调用可能抛出异常的方法
} catch (...) {
// 记录错误但不传播异常
cerr << "Error closing database connection" << endl;
}
}
void close() {
if (isOpen()) {
// 关闭连接,可能抛出异常
}
}
};派生类的虚函数异常说明必须比基类更严格或相同:
class Base {
public:
virtual void func() noexcept {} // 基类声明不抛出异常
};
class Derived : public Base {
public:
// 错误:派生类异常说明更宽松
// void func() override {} // 隐式允许抛出异常,编译错误
// 正确:派生类异常说明相同或更严格
void func() noexcept override {}
};异常说明是函数重写的一部分,若不匹配会导致编译错误:
class Base {
public:
virtual void f() throw(int) {}
};
class Derived : public Base {
public:
// 错误:异常说明不兼容
// void f() throw(string) override {} // 编译错误
// 正确:异常说明必须兼容
void f() throw(int, string) override {}
};函数指针的异常说明必须与目标函数兼容:
void safeFunc() noexcept {}
void riskyFunc() {}
// 正确:函数指针声明与safeFunc匹配
void (*safePtr)() noexcept = safeFunc;
// 错误:函数指针声明比riskyFunc更严格
// void (*riskyPtr)() noexcept = riskyFunc; // 编译错误
// 正确:函数指针声明与riskyFunc兼容
void (*riskyPtr)() = riskyFunc;在回调函数和函数对象中,异常说明可用于约束行为:
#include <iostream>
using namespace std;
// 定义一个接受noexcept函数指针的函数
void process(void (*func)() noexcept) {
try {
func();
} catch (...) {
cout << "This should never happen!" << endl; // 不会执行
}
}
void safe() noexcept {
cout << "Safe function called" << endl;
}
int main() {
process(safe); // 正确:safe是noexcept函数
return 0;
}
C++11 后,应避免使用 C++98 风格的throw()异常说明,改用noexcept:
// 不推荐
void oldStyle() throw(int);
// 推荐
void newStyle() noexcept;过度使用noexcept可能导致程序在异常发生时意外终止,应遵循:
noexcept标准库容器在移动元素时依赖noexcept说明符,因此自定义类型的移动操作应尽可能noexcept:
class MyClass {
public:
// 移动构造函数声明为noexcept
MyClass(MyClass&& other) noexcept {
// 实现移动语义
}
// 移动赋值运算符声明为noexcept
MyClass& operator=(MyClass&& other) noexcept {
// 实现移动语义
return *this;
}
};C++17 弃用 C++98 风格的动态异常说明(throw()),主要原因是:
noexcept提供了更简单、更高效的机制尽管noexcept是重大改进,但仍存在争议:
throw()与新的noexcept不兼容①异常说明类型:
throw())已弃用②核心规则:
noexcept③最佳实践:
noexceptnoexceptnoexcept通过合理使用异常说明,可提高代码的安全性、可维护性和性能,同时避免因异常处理不当导致的程序崩溃。在现代 C++ 编程中,异常说明仍是一项重要但需谨慎使用的工具。