Универсальный контейнер для нескольких типов данных в C ++ - PullRequest
6 голосов
/ 20 декабря 2011

Используя C ++, я пытаюсь создать универсальный контейнерный класс для обработки нескольких типов данных. Это распространенная проблема с различными решениями, но я не нашел ничего настолько ... интуитивно понятного, как я привык к таким языкам, как Python или даже VB / VBA ...

Итак, вот мой сценарий:

Я построил класс DataContainer на основе boost :: any, который я использую для хранения нескольких типов данных нескольких элементов. Я использую карту, объявленную как:

std::map<std::string, DataContainer* (or DataContainerBase*)>

где DataContainer - это класс, который инкапсулирует объект типа:

std::list<boost::any>

вместе с удобными функциями для управления / доступа к списку.

Однако, в конце концов, я все еще вынужден выполнять преобразования типов вне контейнера данных.

Например, если бы я должен был сохранить список значений int на карте, доступ к ним потребовал бы:

int value = boost::any_cast<int>(map["myValue"]->get());

Я бы предпочел, чтобы код повышения полностью содержался в структуре контейнера данных, поэтому мне нужен только тип:

int value = map["myValue"]->get();

или, в худшем случае:

int value = map["myValue"]->get<int>();

Конечно, я мог бы перечислить свои типы данных и сделать что-то вроде:

int value = map["myValue"]->get( TYPE_INT );

или написать специфичные для типа функции get ():

getInt(), getString(), getBool() ... 

Проблема с двумя последними опциями заключается в том, что они несколько негибки, что требует от меня явного объявления каждого типа, который я хочу сохранить в контейнере. Решение any_cast (которое я реализовал и работает), я думаю, хорошо, просто ... не элегантно? Я не знаю. Кажется, мне не нужно использовать внутреннюю механику также и снаружи.

На мой взгляд, передача значения без объявления типа значения в вызове функции-члену DataContainer потребует решения void * (что нежелательно по очевидным причинам), а использование вызова get () потребует (насколько я могу судить) функция-член "виртуального шаблона", определенная на уровне базового класса, что, конечно, недопустимо.

Как таковое, у меня есть работоспособное решение, и действительно, мое использование в этом случае достаточно ограничено, чтобы большинство любых решений работало хорошо. Но мне интересно, может быть, есть более гибкий способ управления универсальным контейнером данных различного типа, чем этот.

Ответы [ 2 ]

6 голосов
/ 20 декабря 2011

Если вы хотите ввести немного сахара для этого:

int value = boost::any_cast<int>(map["myValue"]->get());

, тогда вы можете захотеть сделать функцию get() для возврата прокси-объекта, определенного + - как это:

struct Proxy {
    boost::any& value;
    Proxy(boost::any& value) : value(value) {}

    template<typename T>
    operator T() {
        return boost::any_cast<T>(value);
    }
};

Тогда этот синтаксис сработает:

int value = map["myValue"]->get();
// returns a proxy which gets converted by any_cast<int>

Однако я рекомендую сохранить ясность и просто использовать этот синтаксис:

int value = map["myValue"]->get<int>();

Здесь get не возвращаетпрокси-объект с методом шаблона, но сам метод шаблона (но делает то же самое, что и оператор преобразования шаблона, показанный выше).

1 голос
/ 03 июня 2013

Сегодня я сделал некоторый исходный код для той цели, которую вы хотите.Я знаю, что этот вопрос очень старый, но, может быть, этот маленький кусочек кода кому-то пригодится.Он в основном основан на наддуве: любой.

/*
 * AnyValueMap.hpp
 *
 *  Created on: Jun 3, 2013
 *      Author: alvaro
 */

#ifndef ANYVALUEMAP_HPP_
#define ANYVALUEMAP_HPP_

#include <map>
#include <boost/any.hpp>

using namespace std;

template <class T>
class AnyValueMap {

public:
    AnyValueMap(){}

    virtual ~AnyValueMap(){}

private:
    map<T, boost::any> container_;

    typedef typename map<T, boost::any>::iterator map_iterator;
    typedef typename map<T, boost::any>::const_iterator map_const_iterator;

public:

    bool containsKey(const T key) const
    {
        return container_.find(key) != container_.end();
    }

    bool remove(const T key)
    {
        map_iterator it = container_.find(key);
        if(it != container_.end())
        {
            container_.erase(it);
            return true;
        }
        return false;
    }

    template <class V>
    V getValue(const T key, const V defaultValue) const
    {
        map_const_iterator it = container_.find(key);
        if(it != container_.end())
        {
            return boost::any_cast<V>(it->second);
        }
        return defaultValue;
    }

    template <class V>
    V getValue(const T key) const
    {
        return boost::any_cast<V>(container_.at(key));
    }

    template <class V>
    void setValue(const T key, const V value)
    {
        container_[key] = value;
    }
};

#endif /* ANYVALUEMAP_HPP_ */

Простой пример использования:

AnyValueMap<unsigned long> myMap;
myMap.setValue<double>(365, 1254.33);
myMap.setValue<int>(366, 55);
double storedDoubleValue = myMap.getValue<double>(365);
int storedIntValue = myMap.getValue<int>(366);
...