C++ std::map holding ANY type of value
基本上,我希望MyClass包含一个将字段名(字符串)映射到任何类型值..为此,我编写了一个单独的myfield类,其中包含类型和值信息。
这就是我目前为止所拥有的:
1 2 3 4 5 6 7 8 9 10 | template <typename T> class MyField { T m_Value; int m_Size; } struct MyClass { std::map<string, MyField> fields; //ERROR!!! } |
但如您所见,映射声明失败,因为我没有为myfield提供类型参数…
所以我想应该是
1 | std::map< string, MyField<int> > fields; |
号
或
1 | std::map< string, MyField<double> > fields; |
但很明显,这破坏了我的整个目的,因为声明的映射只能保存特定类型的myfield。我想要一张能容纳任何类型myfield clas的地图。
我有什么办法能做到这一点吗?
Blindy的回答非常好(+1),但只是为了完成答案:有另一种方法来做它没有图书馆,通过使用动态继承:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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作为默认值,并且只考虑使用其他选项。
要修复最后一个缺点点,可以使用智能指针:
1 2 3 | struct MyClass { std::map<string, std::unique_ptr<MyFieldInterface> > fields; // or shared_ptr<> if you are sharing ownership } |
然而,还有一个潜在的问题:
它强制您使用新建/删除(或使唯一/共享)创建对象。这意味着实际对象是在分配器提供的任意位置(主要是默认位置)的空闲存储(堆)中创建的。因此,由于缓存未命中,查看对象列表的速度往往不如查看对象列表的速度快。
如果您关心的是尽可能快地循环此列表的性能(如果不是,请忽略下面的内容),那么您最好使用boost::variant(如果您已经知道将使用的所有具体类型)或使用某种类型擦除的多态容器。
其思想是,容器将管理相同类型的对象数组,但仍然公开相同的接口。该接口可以是一个概念(使用duck类型技术)或一个动态接口(类似于我的第一个示例中的基类)。其优点是容器将同一类型的对象保存在不同的向量中,因此快速浏览它们。只有从一种类型到另一种类型才不是。
下面是一个例子(图片来自那里):http://bannalia.blogspot.fr/2014/05/fast-monphicy-collections.html
但是,如果您需要保持对象插入的顺序,这种技术就失去了它的兴趣。
无论如何,有几种可能的解决方案,这很大程度上取决于您的需求。如果您对您的案例没有足够的经验,我建议使用我在示例中首次解释的简单解决方案或boost::any/variant。
作为对这个答案的补充,我想指出非常好的博客文章,它总结了所有可以使用的C++类型的删除技术,包括注释和利弊:
- http://talesofcpp.fusionfenix.com/post-16/ep集-nine-erasing-the-concrete
- http://akrzemi1.wordpress.com/2013/11/18/type-eraure-part-i/
- http://akrzemi1.wordpress.com/2013/12/06/type-eraure-part-ii/
- http://akrzemi1.wordpress.com/2013/12/11/type-eraure-part-iii/
- http://akrzemi1.wordpress.com/2014/01/13/type-eraure-part-iv/
使用
http://www.boost.org/doc/libs/1__0/doc/html/variant/misc.html variant.versus-any
编辑:我不能强调,尽管滚动您自己的解决方案看起来很酷,但是使用完整的、适当的实现从长远来看会让您省去很多麻烦。
一般来说,这是正确的,但对于构建整个应用程序所基于的低级基本类型,更是如此。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | class AnyBase { public: virtual ~AnyBase() = 0; }; inline AnyBase::~AnyBase() {} template<class T> class Any : public AnyBase { public: typedef T Type; explicit Any(const Type& data) : data(data) {} Any() {} Type data; }; std::map<std::string, std::unique_ptr<AnyBase>> anymap; anymap["number"].reset(new Any<int>(5)); anymap["text"].reset(new Any<std::string>("5")); // throws std::bad_cast if not really Any<int> int value = dynamic_cast<Any<int>&>(*anymap["number"]).data; |
这在C++ 17中是很简单的。使用std::map+std::any+std::any_cast:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <map> #include <string> #include main() { std::map<std::string, std::any> Notebook; std::string name{"Pluto" }; int year = 2015; Notebook["PetName"] = name; Notebook["Born"] = year; std::string strS = std::any_cast<std::string>(Notebook["PetName"]);; // ="Pluto" int intI = std::any_cast<int>(Notebook["Born"]); // = 2015 } |
C++ 17有一个EDCOX1×0的类型,它拥有不同类型的设备,它比联合要好得多。
对于不在C++ 17上的那些,EDCOX1 OR 1表示实现了相同的机制。
对于那些不使用Boost的用户来说,HTTPS://GITHUBCOM/MAPBOX/Apple实现了一个更为轻量级的EDCOX1版本2,适用于C++ 11和C++ 14,它们看起来非常有前景,文档丰富,重量轻,并且有大量的使用示例。
您还可以使用void*并使用reinterpret_cast将值强制转换回正确的类型。这是C语言中经常使用的回调方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | #include <iostream> #include <unordered_map> #include <string> #include <cstdint> // Needed for intptr_t using namespace std; enum TypeID { TYPE_INT, TYPE_CHAR_PTR, TYPE_MYFIELD }; struct MyField { int typeId; void * data; }; int main() { std::unordered_map<std::string, MyField> map; MyField anInt = {TYPE_INT, reinterpret_cast<void*>(42) }; char cstr[] ="Jolly good"; MyField aCString = { TYPE_CHAR_PTR, cstr }; MyField aStruct = { TYPE_MYFIELD, &anInt }; map.emplace("Int", anInt ); map.emplace("C String", aCString ); map.emplace("MyField" , aStruct ); int intval = static_cast<int>(reinterpret_cast<intptr_t>(map["Int"].data)); const char *cstr2 = reinterpret_cast<const char *>( map["C String"].data ); MyField* myStruct = reinterpret_cast<MyField*>( map["MyField"].data ); cout << intval << ' ' << cstr << ' ' << myStruct->typeId <<":" << static_cast<int>(reinterpret_cast<intptr_t>(myStruct->data)) << endl; } |