关于模板:C ++ std :: map持有任何类型的值

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
}

然而,还有一个潜在的问题:

它强制您使用新建/删除(或使唯一/共享)创建对象。这意味着实际对象是在分配器提供的任意位置(主要是默认位置)的空闲存储(堆)中创建的。因此,由于缓存未命中,查看对象列表的速度往往不如查看对象列表的速度快。

diagram of vector of polymorphic objects

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

diagram of polymorphic container

其思想是,容器将管理相同类型的对象数组,但仍然公开相同的接口。该接口可以是一个概念(使用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/

使用boost::variant(如果您知道可以存储的类型,它提供编译时支持)或boost::any(对于任何类型——但这种情况不太可能发生)。

http://www.boost.org/doc/libs/1__0/doc/html/variant/misc.html variant.versus-any

编辑:我不能强调,尽管滚动您自己的解决方案看起来很酷,但是使用完整的、适当的实现从长远来看会让您省去很多麻烦。boost::any实现了RHS复制构造器(C++ 11),既安全(EDCOX1,3),又不安全(哑铸)值检索,EDCOX1,4,CR,RHS操作数,指针和值类型。

一般来说,这是正确的,但对于构建整个应用程序所基于的低级基本类型,更是如此。


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;
}