C++程序创建自定义异常
《C++程序创建自定义异常》
在C++程序设计中,异常处理是构建健壮系统的重要机制。标准库提供的std::exception
及其派生类(如std::runtime_error
、std::logic_error
)虽然能覆盖大部分场景,但当需要表达特定业务逻辑错误时,自定义异常类显得尤为必要。本文将系统阐述如何创建符合C++规范的自定义异常,并探讨其设计原则与最佳实践。
一、异常处理基础回顾
C++通过try
/catch
/throw
机制实现异常处理。标准异常类位于
头文件,例如:
#include
void validateInput(int value) {
if (value
该示例展示了标准异常的使用方式,但存在两个局限性:错误信息固定且无法携带业务上下文数据。
二、自定义异常设计原则
创建自定义异常需遵循以下原则:
-
继承体系:必须直接或间接继承自
std::exception
- 虚析构函数:确保多态删除安全
- 异常安全:构造函数不应抛出异常
- 信息丰富性:提供有意义的错误描述
三、基础自定义异常实现
最简单的自定义异常只需重写what()
方法:
#include
#include
class DatabaseError : public std::exception {
std::string msg;
public:
explicit DatabaseError(const std::string& errorMsg)
: msg("Database Error: " + errorMsg) {}
const char* what() const noexcept override {
return msg.c_str();
}
};
使用示例:
void connectToDatabase() {
bool success = false; // 模拟连接失败
if (!success) {
throw DatabaseError("Connection timeout");
}
}
int main() {
try {
connectToDatabase();
} catch (const DatabaseError& e) {
std::cerr
四、进阶设计:携带上下文数据
业务异常常需携带额外信息,如错误代码、时间戳等:
#include
#include
#include
class HttpError : public std::exception {
int statusCode;
std::string url;
std::time_t timestamp;
mutable std::string cachedMsg;
std::string generateMessage() const {
std::ostringstream oss;
oss
这种设计允许捕获后访问具体错误属性:
try {
// 模拟HTTP请求
throw HttpError(404, "https://api.example.com/data");
} catch (const HttpError& e) {
std::cout
五、异常层次结构设计
对于复杂系统,建议构建异常层次体系:
// 基础业务异常
class BusinessException : public std::exception {
protected:
std::string component;
public:
explicit BusinessException(const std::string& comp)
: component(comp) {}
const char* what() const noexcept override {
return ("Business Error in " + component).c_str();
}
};
// 支付模块异常
class PaymentError : public BusinessException {
std::string transactionId;
double amount;
public:
PaymentError(const std::string& tid, double amt)
: BusinessException("Payment"), transactionId(tid), amount(amt) {}
const char* what() const noexcept override {
static std::string msg;
msg = "Payment Error [TXN:" + transactionId +
"] Amount:" + std::to_string(amount);
return msg.c_str();
}
double getAmount() const noexcept { return amount; }
};
这种设计允许针对不同模块进行特异性捕获:
try {
processPayment("TXN123", 100.50);
} catch (const PaymentError& e) {
std::cout
六、最佳实践与注意事项
1. 异常规范:C++11起推荐使用noexcept
标记不抛出异常的函数
class NonThrowingException : public std::exception {
public:
const char* what() const noexcept override {
return "This should never throw";
}
};
2. 内存管理:避免在异常构造函数中分配动态内存
// 不良示例 - 可能抛出std::bad_alloc
class BadMemoryException : public std::exception {
char* msg;
public:
BadMemoryException() {
msg = new char[100]; // 可能抛出异常
strcpy(msg, "Memory allocation failed");
}
// ...
};
3. 性能考虑:异常处理应保留给真正异常情况,避免用作流程控制
4. 跨模块异常:当异常需要跨越DLL边界时,确保使用相同的C++运行时
七、与标准库的协同
自定义异常可与标准异常组合使用:
#include
#include
class FileAccessError : public std::runtime_error {
std::filesystem::path filePath;
public:
FileAccessError(const std::filesystem::path& p,
const std::string& msg)
: std::runtime_error(msg), filePath(p) {}
const std::filesystem::path& getPath() const noexcept {
return filePath;
}
};
void readFile(const std::filesystem::path& p) {
std::ifstream file(p);
if (!file) {
throw FileAccessError(p, "Failed to open file");
}
// 文件操作...
}
八、现代C++特性应用
C++17引入的std::variant
和std::expected
提供了替代方案,但异常处理在以下场景仍不可替代:
- 跨调用栈的错误传播
- 需要强制处理的错误情况
- 资源获取即初始化(RAII)失败
九、完整示例:订单处理系统
#include
#include
#include
#include
// 基础异常
class OrderException : public std::exception {
std::string orderId;
protected:
explicit OrderException(const std::string& id)
: orderId(id) {}
std::string prefix() const {
return "Order " + orderId + " Error: ";
}
public:
const std::string& getOrderId() const noexcept {
return orderId;
}
};
// 库存不足异常
class InventoryException : public OrderException {
int required;
int available;
public:
InventoryException(const std::string& id, int req, int avail)
: OrderException(id), required(req), available(avail) {}
const char* what() const noexcept override {
static std::string msg;
msg = prefix() + "Insufficient inventory. Required: " +
std::to_string(required) + ", Available: " +
std::to_string(available);
return msg.c_str();
}
int getRequired() const noexcept { return required; }
int getAvailable() const noexcept { return available; }
};
// 支付失败异常
class PaymentException : public OrderException {
double amount;
std::string reason;
public:
PaymentException(const std::string& id, double amt, const std::string& r)
: OrderException(id), amount(amt), reason(r) {}
const char* what() const noexcept override {
static std::string msg;
msg = prefix() + "Payment failed. Amount: " +
std::to_string(amount) + ", Reason: " + reason;
return msg.c_str();
}
double getAmount() const noexcept { return amount; }
const std::string& getReason() const noexcept { return reason; }
};
class OrderProcessor {
std::vector inventory = {10, 5, 8}; // 模拟库存
public:
void processOrder(const std::string& orderId,
const std::vector& quantities) {
// 检查库存
for (size_t i = 0; i inventory[i]) {
throw InventoryException(orderId, quantities[i], inventory[i]);
}
}
// 模拟支付
bool paymentSuccess = false; // 模拟支付失败
if (!paymentSuccess) {
double total = 0; // 计算总价(简化)
throw PaymentException(orderId, total, "Card declined");
}
// 成功处理...
std::cout orderItems = {3, 2, 1};
try {
processor.processOrder("ORD123", orderItems);
} catch (const InventoryException& e) {
std::cerr
十、总结
自定义异常是C++错误处理的重要工具,合理设计可显著提升代码可维护性。关键要点包括:
- 始终继承自
std::exception
- 提供有意义的错误信息和上下文数据
- 构建合理的异常层次结构
- 遵循异常安全准则
- 与标准异常协同使用
通过系统化的异常设计,开发者能够创建出既符合C++规范又能准确表达业务语义的错误处理机制,为构建健壮的C++应用程序奠定基础。
关键词:C++、自定义异常、异常处理、继承体系、异常安全、标准库、业务逻辑、错误上下文、RAII、现代C++
简介:本文详细阐述C++中自定义异常的设计与实现,涵盖基础实现、进阶设计、层次结构、最佳实践等内容,通过完整示例展示如何创建符合业务需求的异常类,提升程序健壮性和可维护性。