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

使用虚继承时指针返回无效值的问题排查与解决

问题

我有一个存储类,用于存储指向任意类型数据的void指针(我知道这一设计存在问题,但并非由我设计)。编写单元测试时,我发现从存储类中返回数据会触发段错误。通过MS Visual调试器观察到,返回的指针值全部无效。该问题仅在B虚继承自A时出现,请问有人知道原因吗?是否有办法让数据正常取回?

我尝试过不为B使用虚继承,此时指针能正常返回。

代码如下:

#include <iostream>
#include <compare>
#include <string>
#include <unordered_map>
#include <typeindex>
#include <memory>

struct MapKey final
{
    std::string name;
    std::type_index type;
};

std::strong_ordering operator<=>(const MapKey& lhs, const MapKey& rhs)
{
    if (lhs.type < rhs.type)
    {
        return std::strong_ordering::less;
    }

    if (lhs.type > rhs.type)
    {
        return std::strong_ordering::greater;
    }

    return lhs.name <=> rhs.name;
};

bool operator==(const MapKey& lhs, const MapKey& rhs)
{
    return (lhs <=> rhs) == std::strong_ordering::equal;
}

template <typename D>
MapKey makeKey(D* data = nullptr)
{
    auto const& type = (data == nullptr)? typeid(D) : typeid(*data);
    std::string typen{type.name()};
    return MapKey{typen, type};
}

struct Hasher final
{
    auto operator()(const MapKey& key) const -> size_t
    {
        return std::hash<std::type_index>{}(key.type) ^ std::hash<std::string>{}(key.name);
    }
};

class DataMap
{
public:
    template <typename D>
    void setData(std::shared_ptr<D> spData)
    {
        m_dataMap[makeKey<D>(spData.get())] = spData;
    } 

    template <typename D>
    [[nodiscard]] std::shared_ptr<D> getData() const
    {
        auto const data = m_dataMap.find(makeKey<D>());
        if (data!= m_dataMap.end())
        {
            return std::static_pointer_cast<D>(data->second);
        }
        return nullptr;
    }

private:
    using DATA_MAP = std::unordered_map<MapKey, std::shared_ptr<void>, Hasher>;
    DATA_MAP m_dataMap;
};

class A
{
public:
    virtual size_t size() const
    {
        return 1;
    }
};
class B : virtual public A
{
public:
    size_t size() const override
    {
        return 2;
    }
};

int main()
{
    std::shared_ptr<A> spA = std::make_shared<B>(); // base ptr (requirement)

    auto k1 = makeKey<A>(spA.get());
    auto k2 = makeKey<B>();

    auto h1 = Hasher()(k1);
    auto h2 = Hasher()(k2);

    auto const& tAB = typeid(*spA);
    auto const& tB = typeid(B);
    if (tAB!= tB) {
        std::cout << "Different types for tAB & tB!"<< std::endl;
    }

    if (h1!= h2) {
        std::cout << "Different hashes!"<< std::endl;
    }

    DataMap ds;
    ds.setData(spA);
    auto spGet = ds.getData<B>();

    auto const& tGet = typeid(*spGet);  // Segfault
    if (tGet!= tB) {
        std::cout << "Different types for tGet & tB!"<< std::endl;
    }

    if (spGet.get()!= spA.get()) {
        std::cout << "Different pointers!"<< std::endl;
    }

    if (spGet->size()!= 2) {
        std::cout << "Different sizes!"<< std::endl;
    }
}
原因分析
  • 键匹配失败导致返回空指针:调用setData(spA)时,makeKey<A>(spA.get())通过typeid(*spA)获取实际类型B;但getData<B>()调用makeKey<B>()时,未传指针会使用typeid(B)。虽然两者的type_index相同,但MapKey包含的type.name()是编译器实现定义的字符串,虚继承场景下编译器对类名的修饰规则可能不同,导致两个MapKey的name字段不一致,最终find操作失败,返回nullptr,后续访问空指针触发段错误。
  • 指针转换错误:即使键匹配成功,static_pointer_cast无法处理虚继承下的指针偏移——虚继承时基类子对象的地址和派生类完整对象的地址可能不同,直接强制转换会得到错误的指针地址,同样触发段错误。
解决方案

1. 修复MapKey的匹配逻辑

删除MapKey中依赖编译器实现的name字段,仅用标准定义的std::type_index作为键的唯一标识,避免因类名字符串不一致导致的键匹配失败:

struct MapKey final
{
    std::type_index type;
};

std::strong_ordering operator<=>(const MapKey& lhs, const MapKey& rhs)
{
    return lhs.type <=> rhs.type;
};

bool operator==(const MapKey& lhs, const MapKey& rhs)
{
    return lhs.type == rhs.type;
}

template <typename D>
MapKey makeKey(D* data = nullptr)
{
    std::type_index type = data? typeid(*data) : typeid(D);
    return MapKey{type};
}

struct Hasher final
{
    auto operator()(const MapKey& key) const -> size_t
    {
        return std::hash<std::type_index>{}(key.type);
    }
};

2. 使用动态指针转换处理多态类型

将getData中的static_pointer_cast替换为dynamic_pointer_cast,它能自动处理虚继承下的指针偏移,确保转换后的指针地址正确:

template <typename D>
[[nodiscard]] std::shared_ptr<D> getData() const
{
    auto const data = m_dataMap.find(makeKey<D>());
    if (data!= m_dataMap.end())
    {
        return std::dynamic_pointer_cast<D>(data->second);
    }
    return nullptr;
}

3. 统一键生成逻辑(可选)

确保setData和getData使用完全一致的键生成规则,比如setData始终基于实际存储对象的类型生成键:

template <typename D>
void setData(std::shared_ptr<D> spData)
{
    m_dataMap[makeKey<D>(spData.get())] = spData;
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 08:09:59