
在C++中,异常处理机制允许程序在运行时检测到错误或异常情况,并跳转到专门的错误处理代码块中执行。这种机制不仅提高了程序的健壮性,还使得错误处理更加清晰和集中。在上一篇博客中,我们探讨了异常处理的基础知识,包括如何抛出和捕获异常。在本篇博客中,我们将深入探讨C++中的异常类层次,了解标准异常库的结构以及如何自定义异常类层次,以满足特定需求。
C++ 标准库在<exception>头文件中定义了异常类的基类std::exception,所有标准异常类均直接或间接派生自该基类。标准异常类主要分为两大分支:
下图展示了 C++ 标准异常类的简化层次结构:
std::exception
├── std::logic_error
│ ├── std::invalid_argument
│ ├── std::domain_error
│ ├── std::length_error
│ ├── std::out_of_range
│ └── std::future_error (C++11)
├── std::runtime_error
│ ├── std::range_error
│ ├── std::overflow_error
│ ├── std::underflow_error
│ ├── std::system_error (C++11)
│ └── std::ios_base::failure (C++11)
├── std::bad_alloc
│ └── std::bad_array_new_length (C++11)
├── std::bad_cast
├── std::bad_typeid
├── std::bad_exception
├── std::bad_function_call (C++11)
├── std::bad_weak_ptr (C++11)
└── std::bad_variant_access (C++17)①std::exception
所有标准异常类的基类,定义了两个核心接口:
class exception {
public:
exception() noexcept;
exception(const exception&) noexcept;
exception& operator=(const exception&) noexcept;
virtual ~exception();
virtual const char* what() const noexcept; // 返回错误描述信息
};②std::logic_error
逻辑错误的基类,通常表示程序内部逻辑错误,可在开发阶段通过代码审查避免。
class logic_error : public exception {
public:
explicit logic_error(const string& what_arg);
explicit logic_error(const char* what_arg);
};③std::runtime_error
运行时错误的基类,表示程序运行过程中发生的不可预测错误。
class runtime_error : public exception {
public:
explicit runtime_error(const string& what_arg);
explicit runtime_error(const char* what_arg);
};①std::invalid_argument
当函数接收到无效参数时抛出,例如:
#include <stdexcept>
#include <string>
int convertToInt(const std::string& str) {
if (str.empty()) {
throw std::invalid_argument("Empty string cannot be converted to int");
}
// 转换逻辑...
}②std::out_of_range
用于表示越界访问,如std::vector或std::string的非法索引:
#include <stdexcept>
#include <vector>
int getElement(const std::vector<int>& vec, size_t index) {
if (index >= vec.size()) {
throw std::out_of_range("Index out of range");
}
return vec[index];
}③std::bad_alloc
当内存分配失败时抛出,通常由new操作符触发:
try {
while (true) {
char* p = new char[1024*1024]; // 持续分配内存
}
} catch (const std::bad_alloc& e) {
std::cerr << "Memory allocation failed: " << e.what() << std::endl;
}④std::system_error (C++11)
表示与操作系统相关的错误,封装了系统错误码:
#include <system_error>
#include <fstream>
void openFile(const std::string& filename) {
std::ifstream file(filename);
if (!file.is_open()) {
throw std::system_error(errno, std::generic_category(), "Failed to open file");
}
}尽管标准异常类提供了常见错误场景的处理能力,但在实际项目中,我们通常需要:
std::runtime_error或std::logic_error派生,利用已有结构what()方法返回有意义的错误描述①基础实现:从std::runtime_error派生
#include <stdexcept>
#include <string>
#include <iostream>
// 自定义异常类
class DatabaseException : public std::runtime_error {
public:
explicit DatabaseException(const std::string& message)
: std::runtime_error(message) {}
};
class ConnectionFailure : public DatabaseException {
public:
explicit ConnectionFailure(const std::string& server)
: DatabaseException("Connection failed: " + server) {}
};
class QueryError : public DatabaseException {
public:
QueryError(const std::string& query, const std::string& error)
: DatabaseException("Query failed: " + query + ", error: " + error),
query_(query), error_(error) {}
const std::string& getQuery() const { return query_; }
const std::string& getError() const { return error_; }
private:
std::string query_;
std::string error_;
};②使用示例
bool isConnected();
bool executeInternal(const std::string& query);
void executeQuery(const std::string& query) {
if (!isConnected()) {
throw ConnectionFailure("localhost:3306");
}
if (query.empty()) {
throw QueryError(query, "Empty query");
}
// 执行查询...
bool success = executeInternal(query);
if (!success) {
throw QueryError(query, "Syntax error");
}
}
int main() {
try {
executeQuery("SELECT * FROM users");
} catch (const ConnectionFailure& e) {
std::cerr << "Connection error: " << e.what() << std::endl;
} catch (const QueryError& e) {
std::cerr << "Query error: " << e.getError() << " in query: " << e.getQuery() << std::endl;
} catch (const std::exception& e) {
std::cerr << "General error: " << e.what() << std::endl;
}
return 0;
}
// 函数的简单实现(实际项目中需要根据需求实现)
bool isConnected() {
// 模拟连接状态
return false; // 返回false以触发ConnectionFailure异常
}
bool executeInternal(const std::string& query) {
// 模拟查询执行
return !query.empty(); // 空查询返回失败
}
③携带更多上下文信息
class DetailedException : public std::runtime_error {
public:
DetailedException(const std::string& message,
int errorCode,
const std::string& file,
int line)
: std::runtime_error(message),
errorCode_(errorCode),
file_(file),
line_(line) {}
int getErrorCode() const { return errorCode_; }
const std::string& getFile() const { return file_; }
int getLine() const { return line_; }
// 重写what()方法,提供更详细的错误信息
const char* what() const noexcept override {
try {
if (fullMessage_.empty()) {
fullMessage_ = std::string(std::runtime_error::what()) +
", code: " + std::to_string(errorCode_) +
", location: " + file_ + ":" + std::to_string(line_);
}
return fullMessage_.c_str();
} catch (...) {
return std::runtime_error::what();
}
}
private:
int errorCode_;
std::string file_;
int line_;
mutable std::string fullMessage_; // mutable允许在const方法中修改
};
// 使用宏简化异常抛出
#define THROW_DETAILED_EXCEPTION(msg, code) \
throw DetailedException(msg, code, __FILE__, __LINE__)①分类式设计
将异常按错误类型分类,形成树状结构:
CustomException
├── NetworkException
│ ├── ConnectionError
│ ├── TimeoutError
│ └── DNSFailure
└── DatabaseException
├── ConnectionFailure
├── QueryError
└── TransactionError②混合式设计
结合错误类型和错误严重程度:
CustomException
├── FatalError
│ ├── SystemCrash
│ └── ResourceExhaustion
└── RecoverableError
├── NetworkException
│ ├── ConnectionError
│ └── TimeoutError
└── DatabaseException
├── ConnectionFailure
└── QueryError①虚析构函数
确保正确释放派生类资源:
class MyException : public std::exception {
public:
virtual ~MyException() noexcept {} // 虚析构函数
// ...
};②线程安全考虑
异常对象可能在多线程环境中被访问,确保线程安全:
class ThreadSafeException : public std::exception {
public:
const char* what() const noexcept override {
std::lock_guard<std::mutex> lock(mutex_);
return message_.c_str();
}
private:
mutable std::mutex mutex_; // mutable允许在const方法中加锁
std::string message_;
};③避免资源泄漏
异常类应遵循 RAII 原则,确保资源自动释放:
class FileException : public std::exception {
public:
FileException(const std::string& filename, const std::string& message)
: filename_(filename), message_(message) {}
private:
std::string filename_; // 自动管理字符串资源
std::string message_;
};①按引用捕获异常
try {
// ...
} catch (const CustomException& e) { // 按const引用捕获
// 处理自定义异常
} catch (const std::exception& e) {
// 处理标准异常
} catch (...) {
// 处理未知异常
}②保持异常层次简洁
避免过深的继承层次,通常不超过 3 层:
// 推荐
CustomException
├── NetworkException
└── DatabaseException
// 不推荐
CustomException
├── LowLevelException
│ ├── MiddleLevelException
│ │ └── HighLevelException
│ │ ├── NetworkException
│ │ └── DatabaseException③提供清晰的错误信息
异常信息应包含足够的上下文,便于调试:
class FileOpenException : public std::runtime_error {
public:
FileOpenException(const std::string& filename, const std::string& details)
: std::runtime_error("Failed to open file: " + filename + ", details: " + details) {}
};// exception_base.h
#pragma once
#include <exception>
#include <string>
#include <vector>
#include <memory>
#include <sstream>
#include <chrono>
namespace myapp {
// 基础异常类
class BaseException : public std::exception {
public:
BaseException(const std::string& message,
const std::string& file,
int line,
const std::string& function)
: message_(message),
file_(file),
line_(line),
function_(function),
timestamp_(std::chrono::system_clock::now()) {}
virtual ~BaseException() noexcept {}
const char* what() const noexcept override {
try {
if (fullMessage_.empty()) {
std::ostringstream oss;
oss << message_ << "\n";
oss << "Location: " << file_ << ":" << line_ << " in " << function_ << "\n";
oss << "Timestamp: " << getTimestampString() << "\n";
fullMessage_ = oss.str();
}
return fullMessage_.c_str();
} catch (...) {
return message_.c_str();
}
}
const std::string& getMessage() const { return message_; }
const std::string& getFile() const { return file_; }
int getLine() const { return line_; }
const std::string& getFunction() const { return function_; }
std::chrono::system_clock::time_point getTimestamp() const { return timestamp_; }
// 添加调用栈信息
void addStackTrace(const std::string& function,
const std::string& file,
int line) {
stackTrace_.push_back({function, file, line});
}
// 获取调用栈信息
const std::vector<std::tuple<std::string, std::string, int>>& getStackTrace() const {
return stackTrace_;
}
protected:
std::string getTimestampString() const {
auto now = std::chrono::system_clock::to_time_t(timestamp_);
char buffer[26];
ctime_s(buffer, sizeof(buffer), &now);
return std::string(buffer);
}
private:
std::string message_;
std::string file_;
int line_;
std::string function_;
std::chrono::system_clock::time_point timestamp_;
mutable std::string fullMessage_;
std::vector<std::tuple<std::string, std::string, int>> stackTrace_;
};
// 系统异常
class SystemException : public BaseException {
public:
SystemException(const std::string& message,
const std::string& file,
int line,
const std::string& function,
int errorCode)
: BaseException(message, file, line, function),
errorCode_(errorCode) {}
int getErrorCode() const { return errorCode_; }
private:
int errorCode_;
};
// 应用异常
class ApplicationException : public BaseException {
public:
ApplicationException(const std::string& message,
const std::string& file,
int line,
const std::string& function,
const std::string& module)
: BaseException(message, file, line, function),
module_(module) {}
const std::string& getModule() const { return module_; }
private:
std::string module_;
};
// 网络异常
class NetworkException : public ApplicationException {
public:
enum class ErrorType {
CONNECTION_FAILURE,
TIMEOUT,
DNS_FAILURE,
PROTOCOL_ERROR
};
NetworkException(const std::string& message,
const std::string& file,
int line,
const std::string& function,
const std::string& endpoint,
ErrorType errorType)
: ApplicationException(message, file, line, function, "network"),
endpoint_(endpoint),
errorType_(errorType) {}
const std::string& getEndpoint() const { return endpoint_; }
ErrorType getErrorType() const { return errorType_; }
private:
std::string endpoint_;
ErrorType errorType_;
};
// 数据库异常
class DatabaseException : public ApplicationException {
public:
enum class ErrorType {
CONNECTION_FAILURE,
QUERY_ERROR,
TRANSACTION_ERROR,
CONSTRAINT_VIOLATION
};
DatabaseException(const std::string& message,
const std::string& file,
int line,
const std::string& function,
const std::string& database,
ErrorType errorType)
: ApplicationException(message, file, line, function, "database"),
database_(database),
errorType_(errorType) {}
const std::string& getDatabase() const { return database_; }
ErrorType getErrorType() const { return errorType_; }
private:
std::string database_;
ErrorType errorType_;
};
} // namespace myapp
// 辅助宏
#define THROW_BASE_EXCEPTION(msg) \
throw myapp::BaseException(msg, __FILE__, __LINE__, __FUNCTION__)
#define THROW_SYSTEM_EXCEPTION(msg, code) \
throw myapp::SystemException(msg, __FILE__, __LINE__, __FUNCTION__, code)
#define THROW_NETWORK_EXCEPTION(msg, endpoint, type) \
throw myapp::NetworkException(msg, __FILE__, __LINE__, __FUNCTION__, endpoint, type)
#define THROW_DATABASE_EXCEPTION(msg, db, type) \
throw myapp::DatabaseException(msg, __FILE__, __LINE__, __FUNCTION__, db, type)// exception_handler.h
#pragma once
#include "exception_base.h"
#include <iostream>
#include <fstream>
#include <string>
#include <ctime>
namespace myapp {
class ExceptionHandler {
public:
static void logException(const BaseException& e) {
std::cerr << "Exception caught:\n" << e.what() << std::endl;
// 记录调用栈
const auto& stackTrace = e.getStackTrace();
if (!stackTrace.empty()) {
std::cerr << "Stack trace:" << std::endl;
for (const auto& frame : stackTrace) {
std::cerr << " at " << std::get<0>(frame)
<< " (" << std::get<1>(frame)
<< ":" << std::get<2>(frame) << ")" << std::endl;
}
}
// 写入日志文件
writeToLogFile(e);
}
static void handleUncaughtException() {
try {
if (const auto e = std::current_exception()) {
try {
std::rethrow_exception(e);
} catch (const BaseException& e) {
logException(e);
} catch (const std::exception& e) {
std::cerr << "Standard exception caught: " << e.what() << std::endl;
} catch (...) {
std::cerr << "Unknown exception caught" << std::endl;
}
}
} catch (...) {
std::cerr << "Exception handling failed" << std::endl;
}
// 终止程序
std::abort();
}
private:
static void writeToLogFile(const BaseException& e) {
try {
std::ofstream logFile("application.log", std::ios::app);
if (logFile.is_open()) {
logFile << "[" << getCurrentDateTime() << "] "
<< "Exception: " << e.getMessage() << "\n"
<< "Location: " << e.getFile() << ":" << e.getLine()
<< " in " << e.getFunction() << "\n";
// 记录调用栈
const auto& stackTrace = e.getStackTrace();
if (!stackTrace.empty()) {
logFile << "Stack trace:" << std::endl;
for (const auto& frame : stackTrace) {
logFile << " at " << std::get<0>(frame)
<< " (" << std::get<1>(frame)
<< ":" << std::get<2>(frame) << ")" << std::endl;
}
}
logFile << "----------------------------------------\n";
logFile.close();
}
} catch (...) {
// 日志记录失败,忽略
}
}
static std::string getCurrentDateTime() {
auto now = std::time(nullptr);
char buffer[26];
ctime_s(buffer, sizeof(buffer), &now);
std::string datetime(buffer);
// 移除换行符
if (!datetime.empty() && datetime.back() == '\n') {
datetime.pop_back();
}
return datetime;
}
};
} // namespace myapp// main.cpp
#include "exception_base.h"
#include "exception_handler.h"
#include <iostream>
#include <string>
#include <thread>
#include <vector>
namespace myapp {
// 模拟数据库操作
void connectToDatabase(const std::string& dbName) {
if (dbName.empty()) {
THROW_DATABASE_EXCEPTION("Database name cannot be empty", dbName,
DatabaseException::ErrorType::CONNECTION_FAILURE);
}
// 模拟连接失败
if (dbName == "test") {
THROW_DATABASE_EXCEPTION("Connection refused", dbName,
DatabaseException::ErrorType::CONNECTION_FAILURE);
}
std::cout << "Connected to database: " << dbName << std::endl;
}
// 模拟网络请求
void sendNetworkRequest(const std::string& url) {
if (url.empty()) {
THROW_NETWORK_EXCEPTION("URL cannot be empty", url,
NetworkException::ErrorType::PROTOCOL_ERROR);
}
// 模拟超时
if (url.find("timeout") != std::string::npos) {
THROW_NETWORK_EXCEPTION("Request timed out", url,
NetworkException::ErrorType::TIMEOUT);
}
std::cout << "Successfully sent request to: " << url << std::endl;
}
// 业务逻辑
void processData(const std::string& dbName, const std::string& apiUrl) {
try {
connectToDatabase(dbName);
sendNetworkRequest(apiUrl);
} catch (const DatabaseException& e) {
// 添加当前函数到调用栈
const_cast<DatabaseException&>(e).addStackTrace(__FUNCTION__, __FILE__, __LINE__);
std::cerr << "Database operation failed: " << e.getMessage() << std::endl;
throw; // 重新抛出,保留异常类型
} catch (const NetworkException& e) {
const_cast<NetworkException&>(e).addStackTrace(__FUNCTION__, __FILE__, __LINE__);
std::cerr << "Network request failed: " << e.getMessage() << std::endl;
throw;
}
}
} // namespace myapp
int main() {
// 设置全局异常处理器
std::set_terminate(myapp::ExceptionHandler::handleUncaughtException);
try {
myapp::processData("production", "https://api.example.com/data");
// 测试异常情况
myapp::processData("test", "https://api.example.com/timeout");
} catch (const myapp::DatabaseException& e) {
myapp::ExceptionHandler::logException(e);
return 1;
} catch (const myapp::NetworkException& e) {
myapp::ExceptionHandler::logException(e);
return 2;
} catch (const myapp::BaseException& e) {
myapp::ExceptionHandler::logException(e);
return 3;
} catch (const std::exception& e) {
std::cerr << "Standard exception: " << e.what() << std::endl;
return 4;
} catch (...) {
std::cerr << "Unknown exception" << std::endl;
return 5;
}
std::cout << "Application completed successfully" << std::endl;
return 0;
}①过度继承:创建过深的异常类层次,导致维护困难
解决方案:保持层次简洁,通常不超过 3 层
②异常信息不足:what()方法返回信息过少,不利于调试
解决方案:在异常类中包含足够的上下文信息,如错误代码、文件位置等
③异常类与错误码混用:同时使用异常和错误码处理错误
解决方案:统一使用异常处理机制,错误码作为异常的属性
④异常安全问题:异常抛出导致资源泄漏或对象状态不一致
解决方案:使用 RAII 技术管理资源,确保异常安全
①异常抛出与捕获的开销:异常处理比条件检查慢
最佳实践:
②异常对象的拷贝开销:抛出和捕获异常时可能涉及对象拷贝
最佳实践:
①跨平台异常处理:不同平台对异常的实现可能存在差异
解决方案:
②与遗留代码的集成:遗留代码可能使用错误码而非异常
解决方案:
C++ 异常类层次结构为程序提供了一种结构化的错误处理方式,通过继承标准异常类并自定义特定领域的异常类,可以构建出既灵活又易于维护的异常处理系统。
关键要点:
std::exception基类派生std::logic_error或std::runtime_error派生,提供清晰的错误信息通过合理设计异常类层次和遵循最佳实践,可以使代码更加健壮、可维护,并提高开发效率。异常处理不仅是错误处理的手段,更是代码设计的重要组成部分。