如何编写继承自std::invalid_argument的高效C++自定义异常类
自定义异常类实现与异常处理方案
一、原有代码的核心问题
- 异常类构造函数没有调用父类
std::invalid_argument的构造函数,不符合C++继承的规范要求。 - 你声明了异常类的构造函数需要接收
const std::string&入参,但抛出异常时写的是const Foo_Exception foo_exc;没有传递任何参数,会直接编译报错。 - 在
setCharacter内部抛出异常后立刻捕获,完全违背异常的设计初衷:异常是用来向能处理错误的上层调用者传递错误信息的,当前层无法处理的错误才需要抛出,当场捕获的写法和直接打印错误信息后返回没有区别,平白增加了异常的开销。 - 自行维护
std::string成员重写what()的逻辑是冗余的,标准库的父类已经实现了对应的能力。
二、符合要求的高效异常类实现
你要求仅执行1次堆分配,提前计算拼接内容总长度做reserve即可实现,完整实现代码如下:
#include <stdexcept> #include <string> #include <unordered_set> #include <iostream> #include <cstdlib> // 全局静态允许字符集合,可根据需求修改 inline const std::unordered_set<char> allowed_chars = {'a', 'b', 'c' /* 其他允许的字符 */}; // 你需要的constexpr常量 constexpr int ERROR_CODE = 40010; class Foo_Exception : public std::invalid_argument { public: explicit Foo_Exception(char invalid_char) : std::invalid_argument(build_error_msg(invalid_char)) {} private: // 消息拼接逻辑放在静态私有函数里,构造初始化列表直接调用 static std::string build_error_msg(char invalid_char) { // 按你的要求预留130000字节空间,仅执行1次堆分配 constexpr size_t RESERVE_SIZE = 130000; std::string msg; msg.reserve(RESERVE_SIZE); msg = "Invalid character passed, error code: "; msg += std::to_string(ERROR_CODE); msg += ", invalid char: '"; msg += invalid_char; msg += "', only characters in allowed set are accepted."; // 其他拼接内容可直接在这里追加,已预留足够空间不会再触发堆分配 return msg; } };
实现说明
- 该类继承
std::invalid_argument完全符合你的需求,std::invalid_argument本身就是用来标识调用者传入非法参数的错误,这类错误确实可通过修正上层调用的传参逻辑避免。 - 不需要自行维护
std::string成员和重写what(),父类std::invalid_argument的构造函数会自动存储传入的错误字符串,父类自带的what()方法会直接返回该字符串的C风格指针,效果和你自行实现的逻辑完全一致,还减少了冗余代码。 - 消息拼接逻辑放到静态私有函数中,在构造函数的初始化列表直接调用,符合C++标准异常类的构造规范。
- 提前reserve足够的内存空间,保证整个拼接过程仅执行1次堆分配,满足你的性能要求。
- 构造函数直接接收非法字符作为入参,使用更简洁,不需要上层调用者自行拼接错误字符串。
三、异常处理位置的选择
绝对不要把try-catch放在setCharacter内部,理由如下:
- 异常的核心作用是把错误信息传递给能处理这个错误的上层,你在setCharacter里当场抛出当场捕获,相当于用异常实现了普通的错误打印逻辑,完全浪费了异常的能力,而且性能比直接打印还差。
- 上层调用者可能有不同的错误处理逻辑:比如有的场景需要打印日志,有的场景需要弹出提示框,有的场景需要回滚操作,你把catch写在setCharacter里相当于写死了处理逻辑,上层无法自定义。
- 正确的做法是把try-catch放到main函数里,或者你上层的业务逻辑入口处,示例代码如下:
void setCharacter(const char& c) { if (allowed_chars.find(c) == allowed_chars.end()) { // 检测到非法参数直接抛异常,不需要在当前层处理 throw Foo_Exception(c); } // 剩下的正常逻辑 std::cout << "Set character: " << c << std::endl; } int main() { try { setCharacter('-'); // 非法字符,会抛异常 } catch(const Foo_Exception& e) { // 上层统一处理异常:比如打印日志、返回错误码等 std::cerr << "Error: " << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
内容的提问来源于stack exchange,提问作者digito_evo
相关产品推荐
相关产品推荐

