在软件开发中,异常处理是保证程序健壮性的重要机制。但很多开发者往往只关注基础语法,忽视了异常处理的规范化设计。我曾在一个大型金融项目中见过这样的代码:整个系统使用整数错误码作为异常,导致维护人员需要翻阅厚厚的错误码手册才能定位问题。这正是缺乏异常规范化的典型后果。
异常规范化主要解决三个核心问题:
虽然已被弃用,但在维护旧代码时仍会遇到。其语法是在函数声明后添加throw(type1, type2):
cpp复制void legacyFunction() throw(std::runtime_error, int) {
// 只能抛出runtime_error或int类型异常
}
重要提示:现代编译器对违反throw规范的行为处理不一致,有些仅警告,有些会直接terminate。在维护旧代码时要特别注意。
cpp复制void safeOperation() noexcept {
// 保证不会抛出任何异常
}
当函数标记为noexcept时:
cpp复制template<typename T>
void swap(T& a, T& b) noexcept(noexcept(a.swap(b))) {
a.swap(b);
}
这种形式在模板元编程中特别有用,可以根据类型特性动态决定是否noexcept。
noexcept带来的性能提升主要来自:
实测数据(GCC 11.2,-O3优化):
| 操作 | noexcept | 非noexcept | 提升 |
|---|---|---|---|
| vector插入 | 15ns | 22ns | 31% |
| 函数调用 | 3ns | 5ns | 40% |
一个完整的自定义异常类应该包含:
cpp复制class BaseException : public std::exception {
mutable std::string msg; // mutable允许const方法修改
int code;
public:
BaseException(int c, std::string m)
: code(c), msg(std::move(m)) {}
const char* what() const noexcept override {
return msg.c_str();
}
int errorCode() const noexcept { return code; }
};
cpp复制class ChainedException : public BaseException {
std::exception_ptr cause;
public:
template<typename E>
ChainedException(int c, std::string m, E&& e)
: BaseException(c, m),
cause(std::make_exception_ptr(std::forward<E>(e))) {}
void rethrowCause() const {
if(cause) std::rethrow_exception(cause);
}
};
cpp复制class AnyException : public std::exception {
std::any storage;
public:
template<typename E>
AnyException(E&& e) : storage(std::forward<E>(e)) {}
template<typename E>
E* getIf() noexcept {
return std::any_cast<E>(&storage);
}
};
推荐的项目级异常层次:
code复制std::exception
├── SystemException
│ ├── IOException
│ ├── NetworkException
│ └── MemoryException
└── BusinessException
├── PaymentException
└── AuthException
cpp复制try {
// ...
}
catch (const NetworkTimeout&) {
// 处理特定网络超时
}
catch (const NetworkException&) {
// 处理一般网络错误
}
catch (const std::exception&) {
// 处理所有标准异常
}
三个级别的异常安全:
示例(强保证实现):
cpp复制class Transaction {
std::vector<Operation> ops;
public:
void addOperation(Operation op) {
auto temp = ops; // 先拷贝
temp.push_back(std::move(op)); // 修改副本
std::swap(ops, temp); // 原子交换
}
};
cpp复制std::expected<Result, Error> parseInput();
cpp复制[[nodiscard]] int calculate() noexcept;
对比不同错误处理方式的性能(纳秒/次):
| 方式 | 成功路径 | 错误路径 |
|---|---|---|
| 异常 | 5 | 5000 |
| 错误码 | 3 | 6 |
| expected | 8 | 10 |
仅在错误率<1%时,异常机制才有性能优势
当通过JNI调用Java代码时:
cpp复制if(jenv->ExceptionCheck()) {
jthrowable je = jenv->ExceptionOccurred();
// 转换为C++异常
throw JavaException(jenv, je);
}
cpp复制class JavaException : public std::runtime_error {
jthrowable je;
public:
JavaException(JNIEnv* env, jthrowable e)
: runtime_error(getMessage(env, e)), je(env->NewGlobalRef(e)) {}
~JavaException() {
jenv->DeleteGlobalRef(je);
}
};
在Linux下获取堆栈信息:
cpp复制#include <execinfo.h>
void printStackTrace() {
void* array[50];
size_t size = backtrace(array, 50);
backtrace_symbols_fd(array, size, STDERR_FILENO);
}
推荐格式:
code复制[2023-08-20 15:30:45] [ERROR] [module]
Exception: FileNotFound
Code: 404
Message: Config file missing
Stack:
#0 loadConfig(config.cpp:45)
#1 initSystem(system.cpp:102)
C++20引入的源位置信息:
cpp复制throw DatabaseException(
500,
"Connection failed",
std::source_location::current()
);
协程特殊处理:
cpp复制task<void> asyncOp() {
try {
co_await networkCall();
}
catch (const NetworkException& e) {
co_return Result::RetryLater;
}
}
在实际项目中,我建议建立统一的异常处理框架,包含:
这样的框架可以使异常处理从负担变为有价值的调试工具。我曾在一个分布式系统中实现这套机制,使平均故障定位时间从2小时缩短到15分钟。