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

How to use C++ exception handling mechanism

2月18日 17:34

C++ Exception Handling Mechanism

C++ exception handling provides a structured way to handle errors, allowing programs to detect and handle errors at runtime without crashing.

Exception Handling Basics

Basic syntax:

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; }

Standard Exception Classes

Exception class hierarchy:

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

Using standard exceptions:

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"); } // Processing logic } void allocateMemory(size_t size) { try { int* ptr = new int[size]; // Use memory delete[] ptr; } catch (const std::bad_alloc& e) { std::cerr << "Memory allocation failed: " << e.what() << std::endl; throw; } }

Custom Exception Classes

Basic custom exception:

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(); } }; // Usage void riskyOperation() { throw MyException("Something went wrong"); }

Exception with error code:

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; } }; // Usage void queryDatabase() { throw DatabaseException("Connection failed", 1001); } try { queryDatabase(); } catch (const DatabaseException& e) { std::cerr << "Database error " << e.getErrorCode() << ": " << e.what() << std::endl; }

Exception Specifications

noexcept specification:

cpp
// Exception specification before C++11 (deprecated) void oldFunction() throw(std::runtime_error) { // May throw runtime_error } // C++11 noexcept void safeFunction() noexcept { // Guaranteed not to throw } // Conditional noexcept template <typename T> void conditionalNoexcept(T&& value) noexcept(std::is_nothrow_move_constructible_v<T>) { // Depends on whether T's move constructor is noexcept }

Purpose of noexcept:

  • Optimize compiler-generated code
  • Allow standard library to choose more efficient implementations (e.g., vector move)
  • Provide better error handling guarantees

RAII and Exception Safety

Exception safety levels:

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() { // Even if exception is thrown, object remains in valid state int* temp = new int; delete resource1; resource1 = temp; // If subsequent operation throws, resource1 is updated, object still valid } };

2. Strong Guarantee:

cpp
class StrongGuaranteeExample { private: std::vector<int> data; public: void update(const std::vector<int>& newData) { // Create copy std::vector<int> temp = data; // Modify copy temp.insert(temp.end(), newData.begin(), newData.end()); // Atomic swap std::swap(data, temp); // If exception is thrown, original data is unaffected } };

3. No-throw Guarantee:

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

Smart Pointers and Exception Safety

Using smart pointers for exception safety:

cpp
// Not recommended: manual memory management void unsafeFunction() { int* ptr = new int(42); // If exception is thrown here, ptr will leak someRiskyOperation(); delete ptr; } // Recommended: use smart pointers void safeFunction() { auto ptr = std::make_unique<int>(42); // Even if exception is thrown, ptr is automatically released someRiskyOperation(); }

std::lock_guard and exception safety:

cpp
std::mutex mtx; void threadSafeOperation() { std::lock_guard<std::mutex> lock(mtx); // Critical section code // Even if exception is thrown, lock is automatically released riskyOperation(); }

Exception Catching and Rethrowing

Catch and rethrow:

cpp
void process() { try { riskyOperation(); } catch (const std::exception& e) { // Log error logError(e.what()); // Rethrow throw; } } // Use std::current_exception to save exception std::exception_ptr currentException; void saveException() { try { riskyOperation(); } catch (...) { currentException = std::current_exception(); } } void rethrowException() { if (currentException) { std::rethrow_exception(currentException); } }

Exceptions with Constructors and Destructors

Exceptions in constructors:

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 subsequent operation fails, constructor throws exception if (size == 0) { delete[] data; // Clean up allocated resources throw std::invalid_argument("Size cannot be zero"); } } ~MyClass() { delete[] data; } };

Exceptions in destructors:

cpp
class MyClass { private: std::unique_ptr<int> ptr; public: ~MyClass() noexcept { // Destructors should not throw exceptions try { cleanup(); } catch (...) { // Swallow exception or log std::cerr << "Exception in destructor" << std::endl; } } void cleanup() { // Cleanup logic } };

Exceptions and Function Pointers

Exception specifications and function pointers:

cpp
// noexcept function pointer using NoThrowFunction = void(*)() noexcept; void safeFunction() noexcept { // Does not throw } void unsafeFunction() { // May throw } NoThrowFunction func1 = safeFunction; // OK // NoThrowFunction func2 = unsafeFunction; // Compilation error

Best Practices

1. Throw by value, catch by reference

cpp
// Recommended throw MyException("Error message"); try { riskyOperation(); } catch (const MyException& e) { // Catch by reference std::cerr << e.what() << std::endl; } // Not recommended throw new MyException("Error message"); // Don't throw pointers

2. Catch the most specific exceptions

cpp
try { riskyOperation(); } catch (const std::invalid_argument& e) { // Handle specific exception } catch (const std::runtime_error& e) { // Handle runtime errors } catch (const std::exception& e) { // Handle other standard exceptions } catch (...) { // Handle unknown exceptions }

3. Use RAII to ensure resource release

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); } } // Disable copy FileHandler(const FileHandler&) = delete; FileHandler& operator=(const FileHandler&) = delete; };

4. Use exceptions only for exceptional situations

cpp
// Recommended: exceptions for real errors void processValue(int value) { if (value < 0) { throw std::invalid_argument("Value must be non-negative"); } // Normal processing } // Not recommended: exceptions for control flow void findElement(const std::vector<int>& vec, int target) { for (int val : vec) { if (val == target) { throw FoundException(); // Don't do this } } }

5. Provide meaningful error messages

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) {} }; // Usage throw DatabaseException("query", "connection timeout");

Notes

  • Don't throw exceptions in destructors
  • When constructor throws, ensure constructed members are properly destructed
  • Exception handling adds runtime overhead, avoid overuse in performance-critical paths
  • noexcept functions that throw will call std::terminate
  • Throwing exceptions across DLL boundaries may cause issues
  • Exception objects should be lightweight, avoid including large data in exceptions
  • Consider using error codes or std::optional as alternatives to exceptions
  • In multithreaded environments, exceptions only propagate within the current thread
标签:C++