C std::map 持有任何类型的值

新手上路,请多包涵

基本上我希望 MyClass 包含一个将字段名称(字符串)映射到任何类型的值的 Hashmap。为此,我编写了一个单独的 MyField 类来保存类型和值信息。

这是我到目前为止所拥有的:

 template <typename T>
class MyField {
    T m_Value;
    int m_Size;
}

struct MyClass {
    std::map<string, MyField> fields;   //ERROR!!!
}

但正如你所见,map 声明失败,因为我没有为 MyField 提供类型参数…

所以我想它必须是这样的

std::map< string, MyField<int> > fields;

或者

std::map< string, MyField<double> > fields;

但这显然破坏了我的整个目的,因为声明的地图只能包含特定类型的 MyField 。我想要一个可以包含任何类型的 MyField 类的地图。

有什么办法可以做到这一点..?

原文由 user3794186 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 682
2 个回答

Blindy 的答案非常好(+1),但只是为了完成答案:还有另一种方法可以在没有库的情况下使用动态继承:

 class MyFieldInterface
{
    int m_Size; // of course use appropriate access level in the real code...
    ~MyFieldInterface() = default;
}

template <typename T>
class MyField : public MyFieldInterface {
    T m_Value;
}

struct MyClass {
    std::map<string, MyFieldInterface* > fields;
}

优点:

  • 任何 C++ 编码人员都熟悉它
  • 它不会强迫你使用 Boost(在某些情况下你是不允许的);

缺点:

  • 您必须在堆/空闲存储上分配对象并使用引用语义而不是值语义来操作它们;
  • 以这种方式公开的公共继承可能会导致过度使用动态继承以及许多与您的类型相关的长期问题确实过于相互依赖;
  • 如果指针向量必须 拥有 对象,则指针向量是有问题的,因为您必须管理销毁;

因此,如果可以,请使用 boost::any 或 boost::variant 作为默认值,否则仅考虑此选项。

要解决最后一个缺点,您可以使用智能指针:

 struct MyClass {
    std::map<string, std::unique_ptr<MyFieldInterface> > fields;  // or shared_ptr<> if you are sharing ownership
}

然而,还有一个潜在的更成问题的地方:

它强制您使用 new/delete(或 make_unique/shared)创建对象。这意味着实际对象是在分配器提供的任何位置(大多数是默认位置)的空闲存储(堆)中创建的。因此,由于 缓存未命中,经常浏览对象列表并没有可能那么快。

多态对象向量图

如果您关心尽可能快地循环遍历此列表的性能(如果不是,请忽略以下内容),那么您最好使用 boost::variant (如果您已经知道您将使用的所有具体类型)或使用某种类型擦除的多态容器。

多态容器示意图

这个想法是容器将管理相同类型的对象数组,但仍然公开相同的接口。该接口可以是一个概念(使用鸭子类型技术)或动态接口(如我的第一个示例中的基类)。优点是容器会将相同类型的对象保存在单独的向量中,因此通过它们很快。只有从一种类型转换到另一种类型不是。

这是一个例子(图片来自那里): http ://bannalia.blogspot.fr/2014/05/fast-polymorphic-collections.html

但是,如果您需要保持插入对象的顺序,这种技术就会失去兴趣。

无论如何,有几种可能的解决方案,这在很大程度上取决于您的需求。如果您对自己的案例没有足够的经验,我建议使用我在示例中首先解释的简单解决方案或 boost::any/variant。


作为对这个答案的补充,我想指出非常好的博客文章,这些文章总结了您可以使用的所有 C++ 类型擦除技术,并附有评论和优缺点:

原文由 Klaim 发布,翻译遵循 CC BY-SA 3.0 许可协议

这在 C++ 17 中很简单。使用 std::map + std::any + std::any_cast:

 #include <map>
#include <string>
#include <any>

int main()
{
    std::map<std::string, std::any> notebook;

    std::string name{ "Pluto" };
    int year = 2015;

    notebook["PetName"] = name;
    notebook["Born"] = year;

    std::string name2 = std::any_cast<std::string>(notebook["PetName"]); // = "Pluto"
    int year2 = std::any_cast<int>(notebook["Born"]); // = 2015
}

原文由 Amit G. 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题