C++ 具有任意类型值的关联数组

发布于 2024-07-10 17:40:43 字数 569 浏览 8 评论 0原文

在 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 中放入的类型越多,数组就会变得越臃肿。

还有更好的建议吗?

What is the best way to have an associative array with arbitrary value types for each key in C++?

Currently my plan is to create a "value" class with member variables of the types I will be expecting. For example:

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;

A downside with this is you have to know the type when accessing the "Value". i.e.:

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

Also the more types that are put in Value, the more bloated the array will be.

Any better suggestions?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

迷爱 2024-07-17 17:40:43

boost::variant 似乎正是您正在寻找的。

boost::variant seems exactly what you are looking for.

拔了角的鹿 2024-07-17 17:40:43

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

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

一些答案建议使用 boost::变体来解决这个问题。 但它不会让您在地图中存储任意类型的值(就像您想要的那样)。 您必须事先知道可能的类型集。 鉴于此,您可以更轻松地执行上述操作:

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<< 。 重要的是要理解,如果您想保存变体中当前包含的内容,您仍然必须知道类型,就像在 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 符合标准,但即使对于小型类型也使用动态内存(因此速度较慢。variant可以将堆栈用于小型类型)。 所以你必须权衡你所使用的东西。

如果您确实想将对象放入其中,而这些对象仅在执行某些操作的方式上有所不同,那么多态性是更好的方法。 您可以拥有一个从中派生的基类:

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 手册。

Your approach was basically into the right direction. You will have to know the type you put into. You can use boost::any and you will be able to put just about anything into the map, as long as you know what you put into:

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

Some answers recommended the use of boost::variant to solve this problem. But it won't let you store arbitrary typed values in the map (like you wanted). You have to know the set of possible types before-hand. Given that, you can do the above more easily:

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"];

That works because boost::variant overloads operator<< for that purpose. It's important to understand that if you want to save what is currently contained in the variant, you still have to know the type, as with in the boost::any case:

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"]);

The order of assignments to a variant is a runtime property of the control flow of your code, but the type used of any variable is determined at compile time. So if you want to get the value out of the variant, you have to know its type. An alternative is to use visitation, as outlined by the variant documentation. It works because the variant stores a code which tells it which type was last assigned to it. Based on that, it decides at runtime which overload of the visitor it uses. boost::variant is quite big and is not completely standard compliant, while boost::any is standard compliant but uses dynamic memory even for small types (so it's slower. variant can use the stack for small types). So you have to trade off what you use.

If you actually want to put objects into it which differ only in the way they do something, polymorphism is a better way to go. You can have a base-class which you derive from:

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

Which would basically require this class layout:

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

class Apple : public Base {
public:
    virtual void print() {
        // print us out.
    }
};

The boost::shared_ptr is a so-called smart pointer. It will delete your objects automatically if you remove them out of your map and nothing else is referencing them them anymore. In theory you could have worked with a plain pointer too, but using a smart pointer will greatly increase safety. Read the shared_ptr manual i linked to.

花开柳相依 2024-07-17 17:40:43

使用 IntValueStringValue 等对 Value 进行子类化。

Subclass Value with IntValue, StringValue, and so on.

别靠近我心 2024-07-17 17:40:43

您可以使用 std::map 的联合吗?

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

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

Can you use a union with std::map?

Boost::variant provides typeless variables.

Altrnatively you could make all your Value data members private and provide accessors that return an error (or throw) if it isn't set.

寒冷纷飞旳雪 2024-07-17 17:40:43

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

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

A straight-forward optimisation would be to use a union, since you'll always only have one of the values as key.

A more complete solution would encapsulate some run time type information into a interface. Primarily "Which type is this?" and "How do I compare values for equality?" Then use implementations of that as key.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文