【问题标题】:C++ associative array with arbitrary types for values具有任意值类型的 C++ 关联数组
【发布时间】:2010-09-28 16:32:51
【问题描述】:

在 C++ 中为每个键创建一个具有任意值类型的关联数组的最佳方法是什么?

目前我的计划是创建一个“值”类,其中包含我所期望的类型的成员变量。例如:

class Value {

    int iValue;
    Value(int v) { iValue = v; }

    std::string sValue;
    Value(std::string v) { sValue = v; }

    SomeClass *cValue;
    Value(SomeClass *v) { cValue = c; }

};

std::map<std::string, Value> table;

这样做的一个缺点是您在访问“值”时必须知道类型。即:

table["something"] = Value(5);
SomeClass *s = table["something"].cValue;  // broken pointer

另外,Value 中放入的类型越多,数组就越臃肿。

有更好的建议吗?

【问题讨论】:

  • 顺便说一句,std::map 不是哈希表;它通常实现为红黑树,但无论如何,键都是按顺序排列的。有std::tr1::unordered_map,通常实现为哈希表。
  • 克里斯,感谢您指出这一点。我现在称它为“关联数组”。

标签: c++ arrays stl boost


【解决方案1】:

将Value 子类化为IntValue、StringValue 等等。

【讨论】:

    【解决方案2】:

    您的方法基本上朝着正确的方向发展。你将必须知道你输入的类型。您可以使用boost::any,并且您可以将任何内容放入地图中,只要您知道自己放入了什么:

    std::map<std::string, boost::any> table;
    table["hello"] = 10;
    std::cout << boost::any_cast<int>(table["hello"]); // outputs 10
    

    一些答案​​建议使用boost::variant 来解决这个问题。但它不会让您在地图中存储任意类型的值(如您所愿)。您必须事先知道可能的类型集。鉴于此,您可以更轻松地完成上述操作:

    typedef boost::variant<int, std::string, void*> variant_type;
    std::map<std::string, variant_type> table;
    table["hello"] = 10;
    // outputs 10. we don't have to know the type last assigned to the variant
    // but the variant keeps track of it internally.
    std::cout << table["hello"];
    

    这很有效,因为boost::variant 重载了operator&lt;&lt; 用于此目的。重要的是要了解,如果您想保存当前包含在变体中的内容,您仍然必须知道类型,就像在 boost::any 案例中一样:

    typedef boost::variant<int, std::string, void*> variant_type;
    std::map<std::string, variant_type> table;
    table["hello"] = "bar";
    std::string value = boost::get<std::string>(table["hello"]);
    

    变量的赋值顺序是代码控制流的运行时属性,但任何变量使用的类型都是在编译时确定的。所以如果你想从变量中获取值,你必须知道它的类型。另一种方法是使用访问,如变体文档所述。它之所以起作用,是因为该变体存储了一个代码,该代码告诉它最后分配给它的类型。基于此,它在运行时决定它使用哪个访问者的重载。 boost::variant 相当大且不完全符合标准,而boost::any 符合标准但即使对于小类型也使用动态内存(因此速度较慢。变体可以将堆栈用于小类型)。所以你必须权衡你使用的东西。

    如果你真的想把对象放入其中,只是它们做某事的方式不同,多态是一个更好的方法。您可以拥有一个派生自的基类:

    std::map< std::string, boost::shared_ptr<Base> > table;
    table["hello"] = boost::shared_ptr<Base>(new Apple(...));
    table["hello"]->print();
    

    这基本上需要这个类布局:

    class Base {
    public:
        virtual ~Base() { }
        // derived classes implement this:
        virtual void print() = 0;
    };
    
    class Apple : public Base {
    public:
        virtual void print() {
            // print us out.
        }
    };
    

    boost::shared_ptr 是所谓的智能指针。如果您将它们从地图中删除,并且没有其他东西再引用它们,它将自动删除您的对象。理论上,您也可以使用普通指针,但使用智能指针将大大提高安全性。阅读我链接到的 shared_ptr 手册。

    【讨论】:

    • 这是运行时类型检查选项。对于编译时类型检查选项,还有 boost::variant (与访问者模式一起使用以实现无懈可击的静态类型检查解决方案)。
    • 好吧,我们在 irc 中讨论过,我认为 :) 只是为了所有其他阅读它的人:variant 只能存储一组有限的类型,如果想要从条目中获取值,可以使用访问(并且会在不知道其类型的情况下获取值),但不能将值保存在任何地方。
    • 因为决定调用访问者的哪个版本是在运行时完成的。但是,变量的类型是在编译时确定的。所以你不能做 SomeAutoDeducedType result = boost::apply_visitor( my_visitor(), u ); .访问者的所有 op() 都得到相同的返回类型。
    【解决方案3】:

    你可以使用联合与 std::map 吗?

    Boost::variant 提供无类型变量。

    或者,您可以将所有值数据成员设为私有,并提供在未设置时返回错误(或抛出)的访问器。

    【讨论】:

    • 您可以将联合与 std::map 一起使用,但联合只能保存 POD 类型的对象。
    【解决方案4】:

    直接的优化是使用union,因为您总是只有一个值作为键。

    更完整的解决方案是将一些运行时类型信息封装到接口中。主要是“这是哪种类型?”和“我如何比较平等的价值观?”然后使用它的实现作为键。

    【讨论】:

    • 联合不能容纳 std::string 的
    • 确实,g++ 会抱怨“错误:成员‘std::string [...]’,并且在联合中不允许使用构造函数”(除其他外)。我的 C++ 生锈了:-/
    【解决方案5】:

    boost::variant 似乎正是您想要的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-01-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-24
      相关资源
      最近更新 更多