使用虚继承时指针返回无效值的问题排查与解决
问题
我有一个存储类,用于存储指向任意类型数据的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
相关产品推荐
相关产品推荐

