You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C++构造函数内添加实例至std::unordered_map时触发空指针解引用错误

问题分析与修复

原代码的核心错误

  1. 构造函数委托错误
    原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)) {}
  1. 全局对象的多副本问题
    头文件中定义的const Error全局对象(如ERR_ERROR_CODE_NOT_FOUND)会在每个包含头文件的编译单元中生成副本,导致allErrors被重复填充,且多个副本的code可能冲突。
    修复方式:将这些全局对象改为inline const(C++17及以后支持),或移至error_codes.cpp中定义。

  2. 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;
}
  1. 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;
}

方案优势

  1. 结构体替代类:更符合值类型语义,代码简洁直观
  2. 自动注册:通过DEFINE_ERROR宏自动将错误实例加入全局map,无需手动维护集合
  3. 常量式访问:支持ErrorCodes::ERR_GLOBAL_ID_NOT_FOUND的直接调用,使用方式和枚举一致
  4. 无野指针风险:错误实例为静态存储期对象,生命周期贯穿整个程序
  5. 唯一错误码:利用__COUNTER__生成唯一负整数错误码,避免与系统退出码冲突

内容的提问来源于stack exchange,提问作者Romolo Caponera

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 11:46:08