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

     2023-02-25     150

关键词:

【中文标题】C++ std::map 持有任何类型的值【英文标题】:C++ std::map holding ANY type of value 【发布时间】:2014-09-02 08:16:46 【问题描述】:

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

这是我目前所拥有的:

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



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

但是如你所见,map 声明失败是因为我没有为 MyField 提供类型参数...

所以我猜它必须是这样的

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

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

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

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

【问题讨论】:

您需要某种类型的擦除。我推荐boost::any 你可以使用std::map&lt;std::string, std::shared_ptr&lt;void&gt;&gt; @sharth 你有什么理由使用 shared_ptr 而不是简单的 (void *)? 我认为使用void *shared_ptr&lt;void&gt; 是一样的)将类型擦除太过分了。你至少需要一个额外的价值来弄清楚那是什么。我要么使用指向基类的指针,要么使用 boost::variant&lt;&gt; 如果它不可用,除非你正在编写非常低级的代码。 如果它真的只是任何MyField&lt;T&gt;,我想另一种选择是每个MyField&lt;T&gt; 继承自的基类。 【参考方案1】:

使用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】:
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;

【讨论】:

您可以在基类中添加一个成员来获取值并避免额外的强制转换((Any&lt;int&gt; &amp;)(*anymap["number"]).data 看起来很糟糕)。但是此时您实现了一个基本的boost::any,还不如使用完整且经过良好测试的类。 +1 不过,创作很到位! 我一定会试试这个!你能向我解释一下 AnyBase 的两个析构函数吗?我不太明白为什么你有两个以及为什么需要它们......谢谢 @user3794186,只有一个析构函数,它是 virtual,因为否则派生类型的析构函数不会被调用(因此 data 析构函数不会被调用)。 @Blindy 哦,我明白了。那么内联析构函数呢..? @user3794186,这只是实现,纯虚析构函数必须在 C++ 中实现。我认为关键是使基类抽象(因此不可创建),更清晰的方法是使用私有构造函数。【参考方案3】:

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,经常浏览对象列表并没有那么快。

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

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

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

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

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


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

http://talesofcpp.fusionfenix.com/post-16/episode-nine-erasing-the-concrete http://akrzemi1.wordpress.com/2013/11/18/type-erasure-part-i/ http://akrzemi1.wordpress.com/2013/12/06/type-erasure-part-ii/ http://akrzemi1.wordpress.com/2013/12/11/type-erasure-part-iii/ http://akrzemi1.wordpress.com/2014/01/13/type-erasure-part-iv/

【讨论】:

【参考方案4】:

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

【讨论】:

s/reinterpret_cast/static_cast/【参考方案5】:

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

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

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

【讨论】:

【参考方案6】:

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

#include <map>
#include <string>
#include <any>
        
int main()

    std::map<std::string, std::any> notebook;

    std::string name "Pluto" ;
    int year = 2015;

    notebook["PetName"] = name;
    notebook["Born"] = year;

    std::string name2 = std::any_cast<std::string>(notebook["PetName"]); // = "Pluto"
    int year2 = std::any_cast<int>(notebook["Born"]); // = 2015

【讨论】:

【参考方案7】:

这是一种幼稚的做法。当然,您可以添加包装器以使某些样板代码无效。

#include <iostream>
#include <memory>
#include <map>
#include <vector>
#include <cassert>


struct IObject

    virtual ~IObject() = default;
;

template<class T>
class Object final : public IObject

public:
    Object(T t_content) : m_context(t_content)
    ~Object() = default;

    const T& get() const
    
        return m_context;
    

private:
    T m_context;
;

struct MyClass

    std::map<std::string, std::unique_ptr<IObject>> m_fields;
;


int main()


    MyClass yourClass;

    // Content as scalar
    yourClass.m_fields["scalar"] = std::make_unique<Object<int>>(35);
    
    // Content as vector
    std::vector<double> v 3.1, 0.042 ;
    yourClass.m_fields["vector"] = std::make_unique<Object<std::vector<double>>>(v);
       
    auto scalar = dynamic_cast<Object<int>*>(yourClass.m_fields["scalar"].get())->get();
    assert(scalar == 35);

    auto vector_ = dynamic_cast<Object<std::vector<double>>*>(yourClass.m_fields["vector"].get())->get();
    assert(v == vector_);

    return 0;

【讨论】:

c++:auto关键字(代码片段)

    前提引入:        1.类型名,在绝大多数编程时,我们都会引入类型来定义一个我们需要的数据。        类型众多,偶尔我们会遇见一串类型名,使用起来无比复杂。存在拼写错误,含义不明确... 查看详情

std::map 中的内存分配

...造成的;我认为它们是由内存分配引起的,但我未能找到任何文献/文档来证明这一点。任何人都可以解决这个问题或指出正确的方向 查看详情

C++ 将预先保留的哈希映射(std::unordered_map)与整数键和连续数据数组(std::vector)进行比较

...时间】:2021-01-1116:35:03【问题描述】:假设使用具有int键类型的哈希映射结 查看详情

Cython C++ 和 std::map 处理

】CythonC++和std::map处理【英文标题】:CythonC++andstd::maphandling【发布时间】:2019-10-0714:11:04【问题描述】:我正在尝试将我的C++类与Cython接口,但难以作为参数传递std::map。sample.pxd文件:fromlibcppcimportboolfromlibcppcimportstringfromlibcppcim... 查看详情

锁定 std::map C++

】锁定std::mapC++【英文标题】:Lockstd::mapC++【发布时间】:2012-07-0812:46:56【问题描述】:在我的多线程应用程序中使用std:map时遇到问题。当线程写入该对象时,我需要锁定地图对象。并行读取此对象的另一个线程应该存储直到写... 查看详情

用于从 std::map 的最后 n 个元素创建 std::vector 的惯用 C++

】用于从std::map的最后n个元素创建std::vector的惯用C++【英文标题】:idiomaticC++forcreatingastd::vectorfromthelastnelementsofastd::map【发布时间】:2012-03-1214:43:44【问题描述】:从std::map的最后n个元素创建std::vector的C++惯用方式是什么?我对... 查看详情

C++ std::map 和 std::vector 的优点? [关闭]

】C++std::map和std::vector的优点?[关闭]【英文标题】:AdvantagesofC++std::mapandstd::vector?[closed]【发布时间】:2013-01-0920:42:42【问题描述】:周末我将参加一个编程比赛,我想知道我应该使用std::vector还是std::map?我会简单地将它们用作... 查看详情

模板类值的 C++ std::map

】模板类值的C++std::map【英文标题】:C++std::mapoftemplate-classvalues【发布时间】:2010-10-0819:38:00【问题描述】:我正在尝试声明一个Row和一个Column类,其中Row有一个私有std::map,其值指向模板化Column。像这样的:template<typenameT>c... 查看详情

最小化锁争用 c++ std::map

】最小化锁争用c++std::map【英文标题】:Minimizelockcontentionc++std::map【发布时间】:2012-02-2903:04:51【问题描述】:我有一个std::map&lt;int,Object*&gt;ObjectMap。现在我需要更新地图,更新可以通过多个线程进行。因此,我们锁定地... 查看详情

std::unordered_map 如何表现? [C++]

】std::unordered_map如何表现?[C++]【英文标题】:Howstd::unordered_mapBehaves?[C++]【发布时间】:2022-01-0816:48:45【问题描述】:我正在阅读有关C++unordered_map的内容,并且有一些我无法清楚回答的问题。我注意到unordered_map为每个索引放置... 查看详情

使用 c++ 11 constexpr 进行 std::map 初始化

】使用c++11constexpr进行std::map初始化【英文标题】:usec++11constexprforstd::mapinitialization【发布时间】:2017-11-1211:12:31【问题描述】:我想用constexpr的键初始化一个std::map。考虑以下C++11MWE:#include<map>usingstd::map;constexprunsignedintstr2i... 查看详情

将 SWIG 与 C++ 的 std::map 一起使用时,Java 没有迭代器

】将SWIG与C++的std::map一起使用时,Java没有迭代器【英文标题】:NoiteratorforJavawhenusingSWIGwithC++\'sstd::map【发布时间】:2012-02-2713:31:44【问题描述】:我已经在C++中实现了一个带有std::map的类,并使用SWIG创建了从Java调用的接口。但... 查看详情

在 C++ std::unordered_map 中预分配桶

】在C++std::unordered_map中预分配桶【英文标题】:Pre-allocatingbucketsinaC++std::unordered_map【发布时间】:2011-08-1919:09:08【问题描述】:我正在使用来自gnu++0x的std::unordered_map来存储大量数据。我想为大量元素预先分配空间,因为我可以... 查看详情

使用 SWIG 和 Python/C API 包装返回 std::map 的函数

...实例的指针。我无法让它与SWIG一起使用,我希望能提供任何帮助。我试图通过一个简单的例子将这个问题归结 查看详情

C++ std::unordered_map 中使用的默认哈希函数是啥?

】C++std::unordered_map中使用的默认哈希函数是啥?【英文标题】:WhatisthedefaulthashfunctionusedinC++std::unordered_map?C++std::unordered_map中使用的默认哈希函数是什么?【发布时间】:2013-10-2501:24:52【问题描述】:我正在使用unordered_map<strin... 查看详情

c++代码中map的find函数问题

#include<iostream>#include<string.h>#include<map>structteststd::map<std::string,int>mapx;test()for(inti=0;i<10;i++)mapx.insert(std::make_pair("xxx",i));;intmain()std::map<int,std::string>::iteratoritr;testx;itr=x.mapx.find(std::string("xxx"));std:... 查看详情

std::map 上的自定义 omp 减少

...对较大的数据,其中始终包含一对标识符和一个与之相关的值。因此,存在相对较少的不同但任意的标识符。在c++中,这可能看起来像std::vector&lt;std::pair&lt;size_t,double& 查看详情

将 std::map 转换为有序的 std::vector

...打印。到目前为止,我的代码是这样的,编译器没有发现任何错误:voidChampionship::orde 查看详情