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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/24702235/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 00:47:58  来源:igfitidea点击:

C++ std::map holding ANY type of value

c++templates

提问by user3794186

Basically I want MyClass that holds a Hashmap that maps Field name(string) to ANY type of Value.. For this purpose I wrote a separate MyField class that holds the type & value information..

基本上我想要 MyClass 持有一个哈希映射,将字段名称(字符串)映射到任何类型的值..为此,我编写了一个单独的 MyField 类来保存类型和值信息..

This is what I have so far:

这是我到目前为止:

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


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

But as you can see, the map declaration fails because I didn't provide the type parameter for MyField...

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

So I guess It has to be something like

所以我想它必须是这样的

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

or

或者

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



But obviously this undermines my whole purpose, because the declared map can only hold MyField of a specific type.. I want a map that can hold ANY type of MyField clas..

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

Is there any way I can achieve this..?

有什么办法可以实现这个..?

回答by Klaim

Blindy's answer is very good (+1), but just to complete the answer: there is another way to do it with no library, by using dynamic inheritance:

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

Pros:

优点:

  • it's familiar to any C++ coder
  • it don't force you to use Boost (in some contexts you are not allowed to);
  • 任何 C++ 编码人员都熟悉它
  • 它不会强迫你使用 Boost(在某些情况下你是不允许的);

Cons:

缺点:

  • you have to allocate the objects on the heap/free store and use reference semantic instead of value semantic to manipulate them;
  • public inheritance exposed that way might lead to over-use of dynamic inheritance and a lot of long-term issues related to your types really being too inter-dependent;
  • a vector of pointers is problematic if it have to ownthe objects, as you have to manage destruction;
  • 您必须在堆/空闲存储上分配对象并使用引用语义而不是值语义来操作它们;
  • 以这种方式公开的公共继承可能会导致过度使用动态继承以及与您的类型相关的许多长期问题,这些问题确实过于相互依赖;
  • 如果指针向量必须拥有对象,则它是有问题的,因为您必须管理销毁;

So use boost::any or boost::variant as default if you can, and consider this option only otherwise.

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

To fix that last cons point you could use smart pointers:

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

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

However there is still a potentially more problematic point:

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

It forces you to create the objects using new/delete (or make_unique/shared). This mean that the actual objects are created in the free store (the heap) at any location provided by the allocator (mostly the default one). Therefore, going though the list of objects very often is not as fast as it could be because of cache misses.

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

diagram of vector of polymorphic objects

多态对象向量图

If you are concerned with performance of looping through this list very often as fast as possible(ignore the following if not), then you'd better use either boost::variant (if you already know all the concrete types you will use) OR use some kind of type-erased polymorphic container.

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

diagram of polymorphic container

多态容器示意图

The idea is that the container would manage arrays of objects of the same type, but that still expose the same interface. That interface can be either a concept (using duck-typing techniques) or a dynamic interface (a base class like in my first example). The advantage is that the container will keep same-type objects in separate vectors, so going through them is fast. Only going from one type to another is not.

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

Here is an example (the images are from there): http://bannalia.blogspot.fr/2014/05/fast-polymorphic-collections.html

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

However, this technique loose it's interest if you need to keep the order in which the objects are inserted.

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

In any way, there are several solutions possible, which depends a lot on your needs. If you have not enough experience with your case, I suggest using either the simple solution I first explained in my example or boost::any/variant.

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



As a complement to this answer, I want to point very good blog articles which summarize all C++ type-erasure techniques you could use, with comments and pros/cons:

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

回答by Amit G.

This is plain in C++ 17. Use std::map + std::any + std::any_cast:

这在 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
}

回答by Blindy

Use either boost::variant(if you know the types you can store, it provides compile time support) or boost::any(for really any type -- but that's kind of unlikely to be the case).

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

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

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

Edit: I cannot emphasize enough that although rolling your own solution might seem cool, using a complete, proper implementation will save you a lot of headache in the long run. boost::anyimplements RHS copy constructors (C++11), both safe (typeid()) and unsafe (dumb casts) value retrievals, with constcorectness, RHS operands and both pointer and value types.

编辑:我再怎么强调也不为过,虽然推出自己的解决方案看起来很酷,但从长远来看,使用完整、正确的实现将为您省去很多麻烦。boost::any实现 RHS 复制构造函数 (C++11),安全 ( typeid()) 和不安全(哑变型)值检索,具有const正确性、RHS 操作数以及指针和值类型。

That's true in general, but even more so for low level, base types you build your entire application on.

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

回答by Neil Kirk

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;

回答by jv-dev

C++17 has a std::varianttype that has facilities for holding different types much better than a union.

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

For those not on C++17, boost::variantimplements this same mechanism.

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

For those not using boost, https://github.com/mapbox/variantimplements a much lighter version of variantfor C++11 and C++14 that looks very promising, well documented, lightweight, and has plenty of usage examples.

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

回答by Stian Svedenborg

You could also use a void* and cast the value back to the correct type using reinterpret_cast. Its a technique often used in C in callbacks.

您还可以使用 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;
}