代码之家  ›  专栏  ›  技术社区  ›  user3794186

C++std::map保存任意类型的值

  •  48
  • user3794186  · 技术社区  · 12 年前

    基本上,我希望MyClass保存一个Hashmap,该Hashmap将Field name(string)映射到任意类型的 价值为此,我编写了一个单独的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类的地图。。

    我有没有办法做到这一点。。?

    8 回复  |  直到 12 年前
        1
  •  65
  •   Amit G.    5 年前

    这在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
    }
    
        2
  •  40
  •   Community Mohan Dere    9 年前

    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

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

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

    然而,如果您需要保持对象的插入顺序,那么这种技术就失去了兴趣。

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


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

        3
  •  19
  •   Blindy    12 年前

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

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

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

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

        4
  •  12
  •   Neil Kirk    12 年前
    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;
    
        5
  •  4
  •   jv-dev    8 年前

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

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

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

        6
  •  1
  •   Stian Svedenborg    12 年前

    您还可以使用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;
    }
    
        7
  •  0
  •   Indri    4 年前

    这是一种天真的做法。当然,您可以添加包装器来取消某些锅炉板代码。

    #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;
    }
    
        8
  •  -2
  •   Edward Finkelstein    4 年前

    工作正在进行中。这种方法的优点是,在执行赋值或下面列出的任何功能时,您不必强制执行任何操作。

    截至目前,它可以:

    • 存储非容器文本类型(constchar*、double、int、float、char、bool)
    • 具有ostream运算符的相应密钥的输出值
    • 重新分配现有键的值
    • 仅使用append方法添加新的键:值对,键不能相同,否则会收到错误消息
    • 使用+运算符添加相同类型的文字

    在代码中,我已经在主函数中演示了它当前可以做什么。

    /*
    This program demonstrates a map of arbitrary literal types implemented in C++17, using any. 
    */
    
    #include <vector>
    #include <any>
    #include <utility>
    #include <iostream>
    using namespace std;
    
    class ArbMap
    {
        public:
        ArbMap() : vec({}), None("None") {} //default constructor
        
        ArbMap(const vector < pair<any,any> > &x) //parametrized constructor, takes in a vector of pairs
        : vec(x), None("None") {}
    
        //our conversion function, this time we pass in a reference
        //to a string, which will get updated depending on which 
        //cast was successful. Trying to return values is ill-advised
        //because this function is recursive, so passing a reference
        //was the next logical solution
        void elem(any &x, string &temp, int num=0 )
        {
            try
            {
                switch (num)
                {
                    case 0:
                    any_cast<int>(x);
                    temp = "i";
                    break;
                    case 1:
                    any_cast<double>(x);
                    temp = "d";
                    break;
                    case 2:
                    any_cast<const char*>(x);
                    temp = "cc";
                    break;
                    case 3:
                    any_cast<char>(x);
                    temp = "c";
                    break;
                    case 4:
                    any_cast<bool>(x);
                    temp = "b";
                    break;
                    case 5:
                    any_cast<string>(x);
                    temp = "s";
                    break;
                }
            } 
            catch(const bad_cast& e)
            {
                elem(x,temp,++num);
            }
            
        }
        //returns size of vector of pairs 
        size_t size()
        {
            return vec.size();
        }
    
    
        /* Uses linear search to find key, then tries to cast 
        all the elements into the appropriate type. */
        any& operator[](any key)
        {
            ArbMap temp;        
            string stemp;
            for (size_t i = 0; i<vec.size(); ++i)
            {
                temp.elem(vec[i].first,stemp);
                if (stemp=="i")
                {
                    try
                    {
                        any_cast<int>(key);
                    }
                    catch(const bad_cast& e)
                    {
                        continue;
                    }
                    if (any_cast<int>(key)==any_cast<int>(vec[i].first))
                    {
                        return vec[i].second;
                    }
                } 
                else if (stemp=="d")
                {
                    try
                    {
                        any_cast<double>(key);
                    }
                    catch(const bad_cast& e)
                    {
                        continue;
                    }
                    if (any_cast<double>(key)==any_cast<double>(vec[i].first))
                    {
                        return vec[i].second;
                    }
                }
                else if (stemp=="cc")
                {
                    try
                    {
                        any_cast<const char*>(key);
                    }
                    catch(const bad_cast& e)
                    {
                        continue;
                    }
                    if (any_cast<const char*>(key)==any_cast<const char*>(vec[i].first))
                    {
                        return vec[i].second;
                    }
                }
                else if (stemp=="c")
                {
                    try
                    {
                        any_cast<char>(key);
                    }
                    catch(const bad_cast& e)
                    {
                        continue;
                    }
                    if (any_cast<char>(key)==any_cast<char>(vec[i].first))
                    {
                        return vec[i].second;
                    }
                }
                else if (stemp=="b")
                {
                    try
                    {
                        any_cast<bool>(key);
                    }
                    catch(const bad_cast& e)
                    {
                        continue;
                    }
                    if (any_cast<bool>(key)==any_cast<bool>(vec[i].first))
                    {
                        return vec[i].second;
                    }
                }
            }
            //vec.push_back({key,None});
            throw -1;
            //return None;
    
        }
        void print();
        void append(any key, any value);
        private:
        vector < pair<any,any> > vec;
        any None;
    };
    
    ostream& operator<<(ostream& out, any a)
    {
        ArbMap temp;        //should be updated to be a smart pointer?
        string stemp;
        temp.elem(a,stemp); //stemp will get updated in the elem function
        
        //The "if else-if ladder" for casting types
        if (stemp=="i") out << any_cast<int>(a); 
        else if (stemp=="d") out << any_cast<double>(a);
        else if (stemp=="cc") out << any_cast<const char*>(a);
        else if (stemp=="c") out << any_cast<char>(a);
        else if (stemp=="b") 
        {
            if (any_cast<bool>(a)==1)
            out << "true";
            else
            out << "false";
        }
        else if (stemp=="s") out << any_cast<string>(a);
    
        return out;
    }
    
    any operator+(any val1, any val2)
    {
        ArbMap temp;        
        string stemp1, stemp2;
        temp.elem(val1,stemp1); 
        temp.elem(val2,stemp2); 
    
        try
        {
            if (stemp1 != stemp2)
                throw -1;
    
            if (stemp1 == "i")
            {
                return any_cast<int>(val1)+any_cast<int>(val2);
            }
            else if (stemp1 == "d")
            {
                return any_cast<double>(val1)+any_cast<double>(val2);
            }
            else if (stemp1 == "cc")
            {
                return string(any_cast<const char*>(val1))+string(any_cast<const char*>(val2));
            }
            else if (stemp1 == "c")
            {
                return string{any_cast<char>(val1)}+string{any_cast<char>(val2)};
            }
            else if (stemp1=="b")
            {
                return static_cast<bool>(any_cast<bool>(val1)+any_cast<bool>(val2));
            }
    
    
        }
        catch (int err)
        {
            cout << "Bad cast! Operands must be of the same 'type'.\n";
           
        }
        
        return val1;
        
    }
    
    void ArbMap::print()
    {
        cout << '\n';
        for (size_t i = 0; i<vec.size(); ++i)
        {
            cout << vec[i].first << ": " << vec[i].second << '\n';
        }
        cout << '\n';
    }
    
    void ArbMap::append(any key, any value)
    {
        try 
        {
            (*this)[key];
            throw "Already exists!";
            
        }
        catch(int error)
        {
            vec.push_back({key,value});
        }
        catch(const char* error)
        {
            cout << "ArbMap::append failed, key already exists!\n";
        }
        
    }
    
    
    
    int main() {
        ArbMap s({{1,2},{"aaa",1.2},{'c',33.3},{"what","this is awesome"}, {true, false}});
        
        cout << s[1] << '\n' << s["aaa"] << '\n' << s['c']
        << '\n' << s["what"] << '\n' 
        //Uncomment the line below and runtime error will occur, as 
        //entry is not in the dictionary
        // << s["not in the dictionary bro"] << '\n'
        << s[true] << '\n';
        
        
        s.print();
        s[1] = "hello";
        s.print();
        s.append(2.3,"what");
        s.print();
        s[2.3] = "hello";
        s.print();
        s.append(2.3,"what");
        s.print();
        s[1] = 1.2;
        s.print();
        s.append(2.4,1.2);
        
        //Operator +
        cout << s[1]+s[2.4] << '\n';
        cout << s["what"] + s[2.3] << '\n';
        s.append('d','a');
        cout << s['c'] << '\n';
        cout <<  s[2.4]+ s["aaa"]+ s['c'] + s['c'] + s['c'] << '\n';
        cout << s[true]+s[true] << '\n';
    
        return 0;
    }
    
    推荐文章