C ++ ассоциативный массив с произвольными типами для значений - PullRequest
7 голосов
/ 29 декабря 2008

Каков наилучший способ иметь ассоциативный массив с произвольными типами значений для каждого ключа в C ++?

В настоящее время я планирую создать класс «value» с переменными-членами типов, которые я буду ожидать. Например:

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;

Недостатком является то, что вы должны знать тип при доступе к «Значение». i.e.:

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

Кроме того, чем больше типов помещено в Value, тем более раздутым будет массив.

Какие-нибудь лучшие предложения?

Ответы [ 5 ]

14 голосов
/ 29 декабря 2008

повышение :: вариант кажется именно то, что вы ищете.

9 голосов
/ 29 декабря 2008

Ваш подход был в основном в правильном направлении. Вы должны знать тип, который вы указали. Вы можете использовать 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<< для этой цели. Важно понимать, что если вы хотите сохранить то, что в данный момент содержится в варианте, вы все равно должны знать тип, как в случае 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, на которое я ссылался.

2 голосов
/ 29 декабря 2008

Можете ли вы использовать объединение с std :: map?

Boost :: option предоставляет переменные без типов.

С другой стороны, вы можете сделать все свои элементы данных Value частными и предоставить средства доступа, которые возвращают ошибку (или выдают ее), если она не установлена.

2 голосов
/ 29 декабря 2008

Подкласс Value с IntValue, StringValue и т. Д.

1 голос
/ 29 декабря 2008

Прямая оптимизация будет заключаться в использовании union, поскольку в качестве ключа у вас всегда будет только одно из значений.

Более полное решение будет включать некоторую информацию о типе времени выполнения в интерфейс. В первую очередь "Какой это тип?" и "Как мне сравнить значения на равенство?" Затем используйте реализации этого в качестве ключа.

...