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

时间:2014-07-11 16:18:35

标签: c++ templates

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

这是我到目前为止所做的:

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


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

但正如您所看到的,地图声明失败,因为我没有为MyField提供类型参数...

所以我猜它必须像

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

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


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

有什么办法可以实现这个目的吗?

6 个答案:

答案 0 :(得分:24)

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)创建对象。这意味着实际对象是在分配器提供的任何位置(主要是默认值)的免费存储(堆)中创建的。因此,由于cache misses,经常访问对象列表的速度并不快。

diagram of vector of polymorphic objects

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

diagram of polymorphic container

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

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

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

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


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

答案 1 :(得分:16)

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

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

编辑:我不能强调,虽然滚动你自己的解决方案看起来很酷,但是从长远来看,使用完整,正确的实现将为你节省很多麻烦。 boost::any实现了RHS复制构造函数(C ++ 11),包括安全(typeid())和不安全(哑元组)值检索,const核心,RHS操作数以及指针和值类型。

一般情况下这是正确的,但对于低级别的基本类型来说更是如此。您可以构建整个应用程序。

答案 2 :(得分:8)

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;

答案 3 :(得分:6)

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

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

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
}

答案 4 :(得分:3)

C ++ 17具有std::variant类型,它具有比联合更好地保存不同类型的工具。

对于那些不在C ++ 17上的人,boost::variant实现了同样的机制。

对于那些没有使用boost的人来说,https://github.com/mapbox/variant为C ++ 11和C ++ 14实现了更轻的variant版本,看起来非常有前途,文档齐全,轻量级,并且有很多用法实例

答案 5 :(得分:0)

您还可以使用void *并使用reinterpret_cast将值强制转换回正确的类型。它是一种常用于回调中的C语言。

#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 << '\n'
         << cstr << '\n'
         << myStruct->typeId << ": " << static_cast<int>(reinterpret_cast<intptr_t>(myStruct->data)) << endl;
}