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

use*_*186 25 c++ templates

基本上我想要的是认为,字段名(字符串)映射到任何类型值的一个HashMap为此我写了持有型和价值信息的单独MyField的MyClass类..

这是我到目前为止:

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


struct MyClass {
    std::map<string, MyField> fields;   //ERROR!!!
}
Run Code Online (Sandbox Code Playgroud)

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

所以我想它必须是这样的

std::map< string, MyField<int> > fields;
Run Code Online (Sandbox Code Playgroud)

要么

std::map< string, MyField<double> > fields;
Run Code Online (Sandbox Code Playgroud)


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

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

Kla*_*aim 27

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;  
}
Run Code Online (Sandbox Code Playgroud)

优点:

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

缺点:

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

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

要修复最后一个缺点,你可以使用智能指针:

struct MyClass {
    std::map<string, std::unique_ptr<MyFieldInterface> > fields;  // or shared_ptr<> if you are sharing ownership
}
Run Code Online (Sandbox Code Playgroud)

但是,仍然存在一个可能更有问题的问题:

它强制您使用new/delete(或make_unique/shared)创建对象.这意味着实际对象是在分配器提供的任何位置(主要是默认值)的空闲存储(堆)中创建的.因此,由于缓存未命中,经常访问对象列表的速度并不快.

多态对象矢量图

如果你经常关注循环遍历此列表的性能(如果没有,则忽略以下内容),那么你最好使用boost :: variant(如果你已经知道将要使用的所有具体类型)或者使用某种类型擦除的多态容器.

多态容器图

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

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

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

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


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


Bli*_*ndy 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),包括safectness,RHS操作数以及指针和值类型的safe(typeid())和不安全(dumb casts)值检索const.

这通常是正确的,但对于低级别的基本类型来说更是如此,您可以构建整个应用程序.


Ami*_* G. 13

这在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
}
Run Code Online (Sandbox Code Playgroud)

  • 为了进一步简化,有没有办法将 `intyear2 = std::any_cast&lt;int&gt;(notebook["Born"]);` 替换为 `autoyear2 = notebook["Born"]);`? (2认同)

Nei*_*irk 9

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;
Run Code Online (Sandbox Code Playgroud)


jv-*_*dev 5

C++17 有一种std::variant类型,它可以比联合更好地保存不同类型。

对于那些不使用 C++17 的人,boost::variant实现相同的机制。

对于那些不使用 boost 的人,https://github.com/mapbox/variantvariant为 C++11 和 C++14实现了一个更轻量级的版本,它看起来非常有前途,文档齐全,轻量级,并且有大量的使用示例。