C++构造函数内添加实例至std::unordered_map时触发空指针解引用错误
问题分析与修复
原代码的核心错误
- 构造函数委托错误
原Error(std::string message)构造函数中直接调用Error(code, message)会创建临时对象,而非初始化当前实例。这导致:
- 当前实例的
code和message未被正确赋值(仍使用默认构造的1和空字符串) - 临时对象的
this被存入allErrors,但临时对象销毁后,map中的指针变为野指针,后续访问触发空指针解引用。
修复方式:改用C++11的委托构造,在初始化列表中调用私有构造函数:
Error::Error(std::string message) : Error(--counter, std::move(message)) {}
全局对象的多副本问题
头文件中定义的const Error全局对象(如ERR_ERROR_CODE_NOT_FOUND)会在每个包含头文件的编译单元中生成副本,导致allErrors被重复填充,且多个副本的code可能冲突。
修复方式:将这些全局对象改为inline const(C++17及以后支持),或移至error_codes.cpp中定义。operator<<的实现错误
成员函数版的operator<<不符合常规使用方式,应改为友元全局函数并标记为const:
// 在类中声明友元 friend std::ostream& operator<<(std::ostream& os, const Error& err); // 在cpp中实现 std::ostream& operator<<(std::ostream& os, const Error& err) { os << "ERR_" << err.code << ": " << err.message; return os; }
- getByCode的返回值问题
返回Error值会导致不必要的拷贝,建议返回const Error&,同时处理未找到的情况时,避免直接exit,可返回ERR_ERROR_CODE_NOT_FOUND的引用。
更优的结构体实现方案
根据你的需求,用结构体替代类,实现自动注册、无需手动维护集合,且支持ErrorCodes::XXX的常量形式,以下是实现方案:
error_codes.h
#include <string> #include <unordered_map> #include <QString> namespace ErrorCodes { struct Error { int code; std::string message; // 类型转换 operator int() const { return code; } operator std::string() const { return message; } operator QString() const { return QString::fromStdString(message); } // 比较运算符 bool operator==(const Error& other) const { return code == other.code; } // 通过错误码获取Error实例 static const Error& getByCode(int code); private: // 内部注册接口 static void registerError(const Error& error); }; // 声明错误码常量(由宏定义生成) extern const Error* ERR_ERROR_CODE_NOT_FOUND; extern const Error* ERR_GLOBAL_ID_NOT_FOUND; extern const Error* ERR_DIR_NOT_FOUND_OR_CREATED; // 自动注册错误码的宏 #define DEFINE_ERROR(name, msg) \ namespace { \ struct Register##name { \ Register##name() { \ static Error error{__COUNTER__ * -1, msg}; \ Error::registerError(error); \ ::ErrorCodes::name = &error; \ } \ } register##name; \ } \ const Error* ErrorCodes::name = nullptr; }
error_codes.cpp
#include "error_codes.h" #include <iostream> #include <QDebug> using namespace ErrorCodes; namespace { std::unordered_map<int, const Error*> errorMap; } void Error::registerError(const Error& error) { errorMap.emplace(error.code, &error); } const Error& Error::getByCode(int code) { auto it = errorMap.find(code); if (it != errorMap.end()) { return *(it->second); } else { qCritical() << QString(*ERR_ERROR_CODE_NOT_FOUND).arg(code); return *ERR_ERROR_CODE_NOT_FOUND; } } // 定义错误码,自动注册到map DEFINE_ERROR(ERR_ERROR_CODE_NOT_FOUND, "Couldn't find error with code %1") DEFINE_ERROR(ERR_GLOBAL_ID_NOT_FOUND, "Problem with the global Id, identifier not found.") DEFINE_ERROR(ERR_DIR_NOT_FOUND_OR_CREATED, "Dir not found or created, check configuration.") // 全局输出运算符 std::ostream& operator<<(std::ostream& os, const Error& err) { os << "ERR_" << err.code << ": " << err.message; return os; }
方案优势
- 结构体替代类:更符合值类型语义,代码简洁直观
- 自动注册:通过
DEFINE_ERROR宏自动将错误实例加入全局map,无需手动维护集合 - 常量式访问:支持
ErrorCodes::ERR_GLOBAL_ID_NOT_FOUND的直接调用,使用方式和枚举一致 - 无野指针风险:错误实例为静态存储期对象,生命周期贯穿整个程序
- 唯一错误码:利用
__COUNTER__生成唯一负整数错误码,避免与系统退出码冲突
内容的提问来源于stack exchange,提问作者Romolo Caponera
相关产品推荐
相关产品推荐

