
C++ 异常处理机制为程序提供了一种处理运行时错误的结构化方式,使代码的错误处理逻辑与正常业务逻辑分离。通过抛出(throw)和捕获(catch)异常,程序能够在错误发生时进行栈展开(Stack Unwinding),并将控制权转移到合适的异常处理代码中。
异常是程序在执行期间发生的特殊情况,如内存不足、文件打开失败、除零错误等。C++ 异常处理机制允许程序在错误发生时跳转到特定的错误处理代码,而不是通过返回错误码层层传递错误信息。
异常处理的三个关键步骤:
throw语句在错误发生点抛出异常对象try-catch块捕获并处理异常try {
// 可能抛出异常的代码
if (error_condition) {
throw ExceptionType("Error message"); // 抛出异常对象
}
// 正常代码
} catch (const ExceptionType1& e) {
// 处理ExceptionType1类型的异常
} catch (const ExceptionType2& e) {
// 处理ExceptionType2类型的异常
} catch (...) {
// 捕获所有异常
// 通常用于资源清理或记录日志
}C++ 标准库提供了一套异常类层次结构,基类为std::exception,位于<exception>头文件中。常见的标准异常类包括:
std::logic_error:逻辑错误(如无效参数)std::runtime_error:运行时错误(如资源耗尽)std::bad_alloc:内存分配失败std::out_of_range:越界访问用户可以通过继承这些标准异常类来创建自定义异常:
#include <stdexcept>
#include <string>
class MyException : public std::runtime_error {
public:
MyException(const std::string& message)
: std::runtime_error(message) {}
};C++ 允许抛出任何类型的异常,但推荐使用类类型异常,因为它们可以携带更多信息并支持继承层次结构。不过,为了演示,我们先看一个抛出内置类型异常的例子:
#include <iostream>
void divide(int a, int b) {
if (b == 0) {
throw 0; // 抛出int类型异常
}
std::cout << "Result: " << a / b << std::endl;
}
int main() {
try {
divide(10, 0);
} catch (int e) {
std::cout << "Error: Division by zero!" << std::endl;
}
return 0;
}
更常见的做法是抛出类类型异常,尤其是从标准异常类派生的自定义异常:
#include <iostream>
#include <stdexcept>
class DivideByZeroException : public std::runtime_error {
public:
DivideByZeroException() : std::runtime_error("Division by zero") {}
};
double safeDivide(double numerator, double denominator) {
if (denominator == 0) {
throw DivideByZeroException(); // 抛出类类型异常
}
return numerator / denominator;
}
int main() {
try {
double result = safeDivide(10.0, 0.0);
std::cout << "Result: " << result << std::endl;
} catch (const DivideByZeroException& e) {
std::cout << "Exception caught: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cout << "General exception: " << e.what() << std::endl;
}
return 0;
}
当抛出异常时,异常对象会被复制到一个特殊的内存区域(异常对象副本),即使原对象在栈展开过程中被销毁,该副本仍然存在。意味着:
class MyException {
public:
MyException() { std::cout << "Constructor" << std::endl; }
MyException(const MyException&) { std::cout << "Copy Constructor" << std::endl; }
~MyException() { std::cout << "Destructor" << std::endl; }
};
void func() {
MyException e;
std::cout << "Throwing exception..." << std::endl;
throw e; // 拷贝构造异常对象副本
}
int main() {
try {
func();
} catch (const MyException& e) {
std::cout << "Exception caught" << std::endl;
}
return 0;
}
当抛出异常时,程序会暂停当前函数的执行,开始沿调用栈向上寻找匹配的 catch 块。这个过程称为栈展开,具体步骤如下:
#include <iostream>
#include <stdexcept>
#include <string>
class Resource {
public:
Resource(const std::string& name) : name(name) {
std::cout << "Resource " << name << " acquired" << std::endl;
}
~Resource() {
std::cout << "Resource " << name << " released" << std::endl;
}
private:
std::string name;
};
void func3() {
Resource r3("r3");
std::cout << "In func3, throwing exception..." << std::endl;
throw std::runtime_error("Exception from func3");
}
void func2() {
Resource r2("r2");
std::cout << "In func2, calling func3..." << std::endl;
func3();
}
void func1() {
Resource r1("r1");
std::cout << "In func1, calling func2..." << std::endl;
try {
func2();
} catch (const std::exception& e) {
std::cout << "Caught exception in func1: " << e.what() << std::endl;
}
}
int main() {
std::cout << "Starting program..." << std::endl;
func1();
std::cout << "Program completed." << std::endl;
return 0;
}
栈展开机制确保了局部对象的析构函数会被正确调用,这为 RAII(资源获取即初始化)技术提供了坚实的基础。通过将资源封装在对象中,可以确保在异常发生时资源被正确释放。
class File {
public:
File(const std::string& filename) : file_ptr(fopen(filename.c_str(), "r")) {
if (!file_ptr) {
throw std::runtime_error("Failed to open file");
}
}
~File() {
if (file_ptr) {
fclose(file_ptr);
}
}
// 禁用拷贝构造和赋值
File(const File&) = delete;
File& operator=(const File&) = delete;
private:
FILE* file_ptr;
};
void processFile(const std::string& filename) {
File file(filename); // 资源获取即初始化
// 处理文件...
// 无论是否抛出异常,File析构函数都会关闭文件
}异常通过 catch 块捕获,语法如下:
catch (ExceptionType1 e) { /* 按值捕获 */ }
catch (ExceptionType2& e) { /* 按引用捕获 */ }
catch (const ExceptionType3& e) { /* 按const引用捕获(推荐) */ }
catch (...) { /* 捕获所有异常 */ }①按值捕获(catch (ExceptionType e)):
②按引用捕获(catch (ExceptionType& e)):
③按 const 引用捕获(catch (const ExceptionType& e)):
④按指针捕获(catch (ExceptionType* e)):
throw &e;),不推荐(容易导致悬空指针)catch 块按顺序匹配,一旦找到匹配的 catch 块,后续的 catch 块将被忽略。因此,捕获派生类异常的 catch 块应放在捕获基类异常的 catch 块之前。
class BaseException : public std::exception {};
class DerivedException : public BaseException {};
void func() {
throw DerivedException();
}
int main() {
try {
func();
} catch (const DerivedException& e) {
std::cout << "Caught DerivedException" << std::endl;
} catch (const BaseException& e) {
std::cout << "Caught BaseException" << std::endl;
} catch (const std::exception& e) {
std::cout << "Caught std::exception" << std::endl;
} catch (...) {
std::cout << "Caught unknown exception" << std::endl;
}
return 0;
}
使用catch (...)可以捕获所有类型的异常,通常用于:
try {
// 可能抛出异常的代码
} catch (...) {
std::cerr << "Unknown exception caught!" << std::endl;
// 资源清理
throw; // 重新抛出异常
}在 catch 块中,可以使用throw;语句将当前捕获的异常重新抛出,让上层调用者处理。重新抛出的异常就是原来的异常对象,不会创建新的异常对象。
try {
// 可能抛出异常的代码
} catch (const std::exception& e) {
std::cerr << "Logging exception: " << e.what() << std::endl;
throw; // 重新抛出,让上层处理
}class DatabaseException : public std::runtime_error {
public:
DatabaseException(const std::string& msg) : std::runtime_error(msg) {}
};
class NetworkException : public std::runtime_error {
public:
NetworkException(const std::string& msg) : std::runtime_error(msg) {}
};
void connectToDatabase() {
// 尝试连接数据库
if (connection_failed) {
throw NetworkException("Database connection failed");
}
}
void processData() {
try {
connectToDatabase();
// 处理数据
} catch (const NetworkException& e) {
// 记录网络异常
std::cerr << "Network error: " << e.what() << std::endl;
// 转换为数据库异常并重新抛出
throw DatabaseException("Database operation failed");
}
}函数测试块允许对整个函数体进行异常捕获,语法如下:
ReturnType FunctionName(Parameters)
try {
// 函数体
} catch (const ExceptionType& e) {
// 异常处理
}函数测试块特别适用于构造函数,因为构造函数没有返回值,无法通过返回错误码表示失败。
构造函数可以抛出异常,但需要注意以下几点:
class MyClass {
private:
Resource* res;
int* data;
public:
MyClass() try : res(new Resource), data(new int[100]) {
// 可能抛出异常的初始化代码
if (initialization_failed) {
throw std::runtime_error("Initialization failed");
}
} catch (const std::exception& e) {
// 清理已分配的资源
delete res;
delete[] data;
throw; // 重新抛出异常
}
~MyClass() {
delete res;
delete[] data;
}
};使用智能指针(如std::unique_ptr和std::shared_ptr)可以自动管理资源,避免手动清理:
#include <memory>
class MyClass {
private:
std::unique_ptr<Resource> res;
std::unique_ptr<int[]> data;
public:
MyClass() : res(std::make_unique<Resource>()), data(std::make_unique<int[]>(100)) {
// 如果构造过程中抛出异常,智能指针会自动释放已分配的资源
if (initialization_failed) {
throw std::runtime_error("Initialization failed");
}
}
};在 C++98/03 中,可以使用异常规范声明函数可能抛出的异常:
void func() throw(std::exception); // 可能抛出std::exception或其子类
void func2() throw(); // 不抛出任何异常但这种异常规范在实践中被证明是有问题的,因此在 C++11 中被弃用,并在 C++17 中移除。
C++11 引入了noexcept说明符,用于声明函数是否会抛出异常:
void func() noexcept; // 承诺不会抛出任何异常
void func2() noexcept(true); // 同上
void func3() noexcept(false); // 可能抛出异常noexcept有两个主要用途:
noexcept的移动构造函数析构函数默认是noexcept(true),即不会抛出异常。如果需要析构函数可能抛出异常,必须显式声明为noexcept(false):
class MyClass {
public:
~MyClass() noexcept(false) {
// 可能抛出异常的析构代码
}
};C++ 提供了三种异常安全级别:
设计函数时应明确提供哪种异常安全保证,并在文档中说明。
①捕获异常后不处理也不重新抛出:
try {
// 代码
} catch (...) {
// 忽略异常,不处理也不重新抛出
}②抛出字符串而非异常类:
throw "Error occurred"; // 不推荐
throw std::runtime_error("Error occurred"); // 推荐③在析构函数中抛出异常:
~MyClass() {
// 可能抛出异常的操作(危险!)
}noexcept说明函数是否会抛出异常#include <iostream>
#include <stdexcept>
#include <memory>
#include <vector>
#include <cstdio> // 提供FILE*和fopen/fclose/fgets等函数
// 自定义异常类
class FileOpenException : public std::runtime_error {
public:
FileOpenException(const std::string& filename)
: std::runtime_error("Failed to open file: " + filename) {}
};
class DatabaseConnectionException : public std::runtime_error {
public:
DatabaseConnectionException(const std::string& msg)
: std::runtime_error("Database error: " + msg) {}
};
// 资源管理类(RAII)
class File {
public:
File(const std::string& filename) : file_ptr(fopen(filename.c_str(), "r")) {
if (!file_ptr) {
throw FileOpenException(filename);
}
}
~File() {
if (file_ptr) {
fclose(file_ptr);
}
}
// 禁用拷贝,支持移动
File(const File&) = delete;
File& operator=(const File&) = delete;
File(File&& other) noexcept : file_ptr(other.file_ptr) {
other.file_ptr = nullptr;
}
File& operator=(File&& other) noexcept {
if (this != &other) {
if (file_ptr) {
fclose(file_ptr);
}
file_ptr = other.file_ptr;
other.file_ptr = nullptr;
}
return *this;
}
bool readLine(char* buffer, size_t size) {
return fgets(buffer, size, file_ptr) != nullptr;
}
private:
FILE* file_ptr;
};
// 数据库连接类
class Database {
public:
Database(const std::string& connectionString)
: connected(false), connectionString(connectionString) {
connect();
}
~Database() {
disconnect();
}
void connect() {
// 模拟连接数据库
if (connectionString.empty()) {
throw DatabaseConnectionException("Invalid connection string");
}
// 模拟连接成功
connected = true;
std::cout << "Connected to database" << std::endl;
}
void disconnect() noexcept {
if (connected) {
// 模拟断开连接
std::cout << "Disconnected from database" << std::endl;
connected = false;
}
}
void executeQuery(const std::string& query) {
if (!connected) {
throw DatabaseConnectionException("Not connected to database");
}
// 模拟查询执行
std::cout << "Executing query: " << query << std::endl;
// 模拟查询失败
if (query.empty()) {
throw std::invalid_argument("Empty query");
}
}
private:
bool connected;
std::string connectionString;
};
// 业务逻辑类
class DataProcessor {
public:
DataProcessor(const std::string& filename, const std::string& dbConnection)
: file(filename), db(dbConnection) {}
void processData() {
try {
char buffer[1024];
while (file.readLine(buffer, sizeof(buffer))) {
// 处理每一行数据
std::string query = "INSERT INTO table VALUES ('" + std::string(buffer) + "')";
db.executeQuery(query);
}
} catch (const std::invalid_argument& e) {
std::cerr << "Invalid query: " << e.what() << std::endl;
// 可以选择继续处理其他数据或终止
throw; // 重新抛出,让上层处理
}
}
private:
File file;
Database db;
};
int main() {
try {
DataProcessor processor("data.txt", "localhost:3306/mydb");
processor.processData();
} catch (const FileOpenException& e) {
std::cerr << "File error: " << e.what() << std::endl;
return 1;
} catch (const DatabaseConnectionException& e) {
std::cerr << "Database error: " << e.what() << std::endl;
return 2;
} catch (const std::exception& e) {
std::cerr << "General error: " << e.what() << std::endl;
return 3;
} catch (...) {
std::cerr << "Unknown error occurred" << std::endl;
return 4;
}
std::cout << "Data processing completed successfully" << std::endl;
return 0;
}
C++ 异常处理机制为程序提供了一种强大而灵活的错误处理方式,通过抛出异常、栈展开和捕获异常,可以将错误处理逻辑与正常业务逻辑分离,提高代码的可读性和可维护性。
关键要点:
throw抛出异常,try-catch捕获异常throw;重新抛出当前异常合理使用异常处理机制,可以使代码更加健壮、可靠,同时保持良好的性能。但需注意,异常不应替代正常的错误检查,而应仅用于处理真正的异常情况。