C++的异常处理机制通过try、catch、throw关键字配合工作,能够在程序运行出现意外情况时捕获并处理错误,避免程序直接崩溃。但在实际开发中,很多开发者会因为对机制理解不深,在异常处理过程中犯下各类错误。

常见异常处理错误
1. 捕获异常后不做任何处理
部分开发者在catch块中只写空语句,既不处理异常也不向上层抛出异常,这会导致错误被静默忽略,后续逻辑可能基于错误状态继续执行,引发更隐蔽的问题。
#include <iostream>
#include <stdexcept>
void divide(int a, int b) {
try {
if (b == 0) {
throw std::runtime_error("除数不能为0");
}
std::cout << "结果:" << a / b << std::endl;
} catch (const std::exception& e) {
// 错误:空catch块,异常被静默忽略
}
}
int main() {
divide(10, 0);
// 后续逻辑可能基于错误状态运行,产生不可预期结果
return 0;
}
正确的做法是在catch块中处理异常,比如记录日志、修正状态,或者重新抛出异常交给上层处理。
#include <iostream>
#include <stdexcept>
void divide(int a, int b) {
try {
if (b == 0) {
throw std::runtime_error("除数不能为0");
}
std::cout << "结果:" << a / b << std::endl;
} catch (const std::exception& e) {
std::cerr << "捕获到异常:" << e.what() << std::endl;
throw; // 重新抛出异常交给上层处理
}
}
2. 抛出不能被正确捕获的异常类型
如果抛出的异常类型没有对应的catch分支,且没有被更通用的异常类型捕获,程序会调用std::terminate直接终止,无法完成后续的清理工作。
#include <iostream>
void func() {
throw 10; // 抛出int类型异常
}
int main() {
try {
func();
} catch (const char* e) { // 只捕获const char*类型异常
std::cout << e << std::endl;
}
// 没有匹配int类型的catch分支,程序会直接终止
return 0;
}
建议优先使用标准异常类型,或者自定义继承自std::exception的异常类,同时可以添加一个捕获std::exception的通用分支作为兜底。
#include <iostream>
#include <stdexcept>
class MyException : public std::exception {
public:
const char* what() const noexcept override {
return "自定义异常发生";
}
};
void func() {
throw MyException();
}
int main() {
try {
func();
} catch (const MyException& e) {
std::cout << e.what() << std::endl;
} catch (const std::exception& e) {
// 兜底捕获所有标准异常
std::cout << "标准异常:" << e.what() << std::endl;
}
return 0;
}
3. 在析构函数中抛出异常
C++规定,如果析构函数在栈展开过程中被调用,此时再抛出异常会导致程序直接调用std::terminate终止。即使不在栈展开过程中,析构函数抛出异常也会增加错误处理的复杂度,容易导致资源泄漏。
#include <iostream>
class Test {
public:
~Test() {
throw std::runtime_error("析构函数抛出异常"); // 错误做法
}
};
int main() {
try {
Test t;
throw std::runtime_error("函数内异常");
} catch (...) {
// 此时析构函数被调用并抛出异常,程序会直接终止
}
return 0;
}
析构函数应该保证不抛出异常,如果内部操作可能失败,应该吞掉异常或者记录日志,不要向外抛出。
#include <iostream>
#include <stdexcept>
class Test {
public:
~Test() {
try {
// 可能失败的操作
} catch (...) {
// 记录日志,不抛出异常
std::cerr << "析构函数内操作失败" << std::endl;
}
}
};
4. 异常捕获顺序错误
catch分支是按照顺序匹配的,如果把捕获基类异常的catch放在捕获派生类异常的前面,会导致派生类异常永远无法被对应的catch分支捕获,只能被基类分支处理,丢失具体的异常信息。
#include <iostream>
#include <stdexcept>
void func() {
throw std::runtime_error("运行时错误");
}
int main() {
try {
func();
} catch (const std::exception& e) { // 基类异常放在前面
std::cout << "基类异常:" << e.what() << std::endl;
} catch (const std::runtime_error& e) { // 派生类异常永远无法命中
std::cout << "运行时错误:" << e.what() << std::endl;
}
return 0;
}
正确的顺序应该是先捕获派生类异常,再捕获基类异常,保证更具体的异常优先被处理。
#include <iostream>
#include <stdexcept>
void func() {
throw std::runtime_error("运行时错误");
}
int main() {
try {
func();
} catch (const std::runtime_error& e) { // 派生类异常放在前面
std::cout << "运行时错误:" << e.what() << std::endl;
} catch (const std::exception& e) { // 基类异常放在后面兜底
std::cout << "基类异常:" << e.what() << std::endl;
}
return 0;
}
5. 抛出局部对象的指针
如果抛出指向局部对象的指针,当异常被抛出时局部对象可能已经被销毁,此时catch块中拿到的是悬空指针,访问它会导致未定义行为。
#include <iostream>
void func() {
int value = 10;
throw &value; // 抛出局部变量的指针
}
int main() {
try {
func();
} catch (int* p) {
// p是悬空指针,访问它会导致未定义行为
std::cout << *p << std::endl;
}
return 0;
}
应该直接抛出异常对象本身,而不是指针,C++的异常处理机制会自动管理异常对象的生命周期,保证catch块中能正确访问。
#include <iostream>
#include <string>
void func() {
std::string msg = "错误信息";
throw msg; // 直接抛出异常对象
}
int main() {
try {
func();
} catch (const std::string& e) {
std::cout << "捕获到异常:" << e << std::endl;
}
return 0;
}
总结
以上几类错误是C++函数异常处理中最常见的问题,核心是开发者对异常的生命周期、匹配规则、使用场景理解不到位。在实际开发中,需要遵循异常处理的规范,避免静默忽略异常、合理设计异常类型、规范析构函数行为,才能充分发挥异常处理机制的作用,提升程序的健壮性。