乐闻世界logo
搜索文章和话题

C++ 异常处理机制如何使用

2月18日 17:34

C++ 异常处理机制

C++ 异常处理提供了一种结构化的错误处理方式,允许程序在运行时检测和处理错误,而不会导致程序崩溃。

异常处理基础

基本语法:

cpp
#include <iostream> #include <stdexcept> int divide(int a, int b) { if (b == 0) { throw std::runtime_error("Division by zero"); } return a / b; } int main() { try { int result = divide(10, 0); std::cout << "Result: " << result << std::endl; } catch (const std::runtime_error& e) { std::cerr << "Error: " << e.what() << std::endl; } catch (...) { std::cerr << "Unknown error occurred" << std::endl; } return 0; }

标准异常类

异常类层次结构:

shell
std::exception ├── std::logic_error │ ├── std::invalid_argument │ ├── std::domain_error │ ├── std::length_error │ └── std::out_of_range └── std::runtime_error ├── std::range_error ├── std::overflow_error └── std::underflow_error

使用标准异常:

cpp
#include <stdexcept> void processValue(int value) { if (value < 0) { throw std::invalid_argument("Value must be non-negative"); } if (value > 100) { throw std::out_of_range("Value must be <= 100"); } // 处理逻辑 } void allocateMemory(size_t size) { try { int* ptr = new int[size]; // 使用内存 delete[] ptr; } catch (const std::bad_alloc& e) { std::cerr << "Memory allocation failed: " << e.what() << std::endl; throw; } }

自定义异常类

基本自定义异常:

cpp
class MyException : public std::exception { private: std::string message; public: MyException(const std::string& msg) : message(msg) {} const char* what() const noexcept override { return message.c_str(); } }; // 使用 void riskyOperation() { throw MyException("Something went wrong"); }

带错误代码的异常:

cpp
class DatabaseException : public std::runtime_error { private: int errorCode; public: DatabaseException(const std::string& msg, int code) : std::runtime_error(msg), errorCode(code) {} int getErrorCode() const noexcept { return errorCode; } }; // 使用 void queryDatabase() { throw DatabaseException("Connection failed", 1001); } try { queryDatabase(); } catch (const DatabaseException& e) { std::cerr << "Database error " << e.getErrorCode() << ": " << e.what() << std::endl; }

异常规范

noexcept 规范:

cpp
// C++11 之前的异常规范(已弃用) void oldFunction() throw(std::runtime_error) { // 可能抛出 runtime_error } // C++11 的 noexcept void safeFunction() noexcept { // 保证不抛出异常 } // 条件 noexcept template <typename T> void conditionalNoexcept(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>) { // 根据 T 的移动构造函数是否 noexcept 来决定 }

noexcept 的作用:

  • 优化编译器生成的代码
  • 允许标准库选择更高效的实现(如 vector 移动)
  • 提供更好的错误处理保证

RAII 与异常安全

异常安全级别:

1. 基本保证(Basic Guarantee):

cpp
class ResourceManager { private: int* resource1; int* resource2; public: ResourceManager() : resource1(nullptr), resource2(nullptr) { resource1 = new int; resource2 = new int; } ~ResourceManager() { delete resource1; delete resource2; } void modify() { // 即使抛出异常,对象仍处于有效状态 int* temp = new int; delete resource1; resource1 = temp; // 如果后续操作抛出异常,资源1已更新,对象仍然有效 } };

2. 强烈保证(Strong Guarantee):

cpp
class StrongGuaranteeExample { private: std::vector<int> data; public: void update(const std::vector<int>& newData) { // 创建副本 std::vector<int> temp = data; // 修改副本 temp.insert(temp.end(), newData.begin(), newData.end()); // 原子交换 std::swap(data, temp); // 如果抛出异常,原数据不受影响 } };

3. 不抛出保证(No-throw Guarantee):

cpp
class NoThrowExample { private: std::unique_ptr<int> ptr; public: void reset() noexcept { ptr.reset(); // unique_ptr::reset 是 noexcept } };

智能指针与异常安全

使用智能指针确保异常安全:

cpp
// 不推荐:手动管理内存 void unsafeFunction() { int* ptr = new int(42); // 如果这里抛出异常,ptr 会泄漏 someRiskyOperation(); delete ptr; } // 推荐:使用智能指针 void safeFunction() { auto ptr = std::make_unique<int>(42); // 即使抛出异常,ptr 也会自动释放 someRiskyOperation(); }

std::lock_guard 与异常安全:

cpp
std::mutex mtx; void threadSafeOperation() { std::lock_guard<std::mutex> lock(mtx); // 临界区代码 // 即使抛出异常,锁也会自动释放 riskyOperation(); }

异常捕获与重新抛出

捕获并重新抛出:

cpp
void process() { try { riskyOperation(); } catch (const std::exception& e) { // 记录日志 logError(e.what()); // 重新抛出 throw; } } // 使用 std::current_exception 保存异常 std::exception_ptr currentException; void saveException() { try { riskyOperation(); } catch (...) { currentException = std::current_exception(); } } void rethrowException() { if (currentException) { std::rethrow_exception(currentException); } }

异常与构造函数/析构函数

构造函数中的异常:

cpp
class MyClass { private: int* data; std::string name; public: MyClass(const std::string& n, size_t size) : name(n) { data = new int[size]; // 如果后续操作失败,构造函数抛出异常 if (size == 0) { delete[] data; // 清理已分配的资源 throw std::invalid_argument("Size cannot be zero"); } } ~MyClass() { delete[] data; } };

析构函数中的异常:

cpp
class MyClass { private: std::unique_ptr<int> ptr; public: ~MyClass() noexcept { // 析构函数不应该抛出异常 try { cleanup(); } catch (...) { // 吞掉异常或记录日志 std::cerr << "Exception in destructor" << std::endl; } } void cleanup() { // 清理逻辑 } };

异常与函数指针

异常规范与函数指针:

cpp
// noexcept 函数指针 using NoThrowFunction = void(*)() noexcept; void safeFunction() noexcept { // 不抛出异常 } void unsafeFunction() { // 可能抛出异常 } NoThrowFunction func1 = safeFunction; // OK // NoThrowFunction func2 = unsafeFunction; // 编译错误

最佳实践

1. 按值抛出,按引用捕获

cpp
// 推荐 throw MyException("Error message"); try { riskyOperation(); } catch (const MyException& e) { // 按引用捕获 std::cerr << e.what() << std::endl; } // 不推荐 throw new MyException("Error message"); // 不要抛出指针

2. 捕获最具体的异常

cpp
try { riskyOperation(); } catch (const std::invalid_argument& e) { // 处理特定异常 } catch (const std::runtime_error& e) { // 处理运行时错误 } catch (const std::exception& e) { // 处理其他标准异常 } catch (...) { // 处理未知异常 }

3. 使用 RAII 确保资源释放

cpp
class FileHandler { private: FILE* file; public: FileHandler(const char* filename) { file = fopen(filename, "r"); if (!file) { throw std::runtime_error("Failed to open file"); } } ~FileHandler() noexcept { if (file) { fclose(file); } } // 禁止拷贝 FileHandler(const FileHandler&) = delete; FileHandler& operator=(const FileHandler&) = delete; };

4. 异常只用于异常情况

cpp
// 推荐:异常用于真正的错误 void processValue(int value) { if (value < 0) { throw std::invalid_argument("Value must be non-negative"); } // 正常处理 } // 不推荐:异常用于控制流 void findElement(const std::vector<int>& vec, int target) { for (int val : vec) { if (val == target) { throw FoundException(); // 不要这样做 } } }

5. 提供有意义的错误信息

cpp
class DatabaseException : public std::runtime_error { public: DatabaseException(const std::string& operation, const std::string& reason) : std::runtime_error("Database error in " + operation + ": " + reason) {} }; // 使用 throw DatabaseException("query", "connection timeout");

注意事项

  • 不要在析构函数中抛出异常
  • 构造函数中抛出异常时,确保已构造的成员被正确析构
  • 异常处理会增加运行时开销,避免在性能关键路径过度使用
  • noexcept 函数如果抛出异常会调用 std::terminate
  • 跨 DLL 边界抛出异常可能导致问题
  • 异常对象应该轻量级,避免在异常中包含大量数据
  • 考虑使用错误码或 std::optional 作为异常的替代方案
  • 在多线程环境中,异常只在当前线程中传播
标签:C++