Гетерогенные контейнеры в C ++ - PullRequest
22 голосов
/ 18 октября 2011

Я видел этот симпатичный рисунок, который классифицирует, какой контейнер STL подойдет, основываясь на различных требованиях к данным, таких как:

- фиксированный размер и переменный размер

- данные того же типа идругой тип

- отсортированные и несортированные данные

- последовательный или произвольный доступ

http://plasmahh.projectiwear.org/cce_clean.svg

Я заметил на этом изображении, что C ++ STLнет контейнера, который

  1. Переменный размер
  2. Гетерогенный (данные разных типов).

Разве в C ++ нет чего-то для этого?

PS - может быть много перестановок, различающих свойства контейнеров, и многие другие тоже могут не предоставляться в STL.

Ответы [ 6 ]

17 голосов
/ 18 октября 2011

Ну, как правило, контейнеры C ++ предназначены для хранения объектов одного типа с использованием шаблонов. Если вам нужны разные типы, которые являются производными от одного типа, вы можете хранить контейнер указателей (я думаю, вы могли бы также иметь контейнер void * на что угодно ...), например. станд :: вектор .

Если вам нужны совершенно не связанные типы, вы можете хранить объекты, которые могут безопасно ссылаться на эти другие типы, такие как boost :: any.

http://www.boost.org/doc/libs/1_47_0/doc/html/any.html

Некоторые примеры с сайта поддержки:

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

using boost::any_cast;
typedef std::list<boost::any> many;

void append_int(many & values, int value)
{
    boost::any to_append = value;
    values.push_back(to_append);
}

void append_string(many & values, const std::string & value)
{
    values.push_back(value);
}

bool is_int(const boost::any & operand)
{
    return operand.type() == typeid(int);
}
bool is_char_ptr(const boost::any & operand)
{
    try
    {
        any_cast<const char *>(operand);
        return true;
    }
    catch(const boost::bad_any_cast &)
    {
        return false;
    }
}

boost :: variable аналогичен, но вы указываете все разрешенные типы, а не разрешаете какой-либо тип в вашем контейнере.

http://www.boost.org/doc/libs/1_47_0/doc/html/variant.html

std::vector< boost::variant<unsigned, std::string> > vec;
vec.push_back( 44);
vec.push_back( "str" );
vec.push_back( SomthingElse(55, 65) ); //not allowed
10 голосов
/ 18 октября 2011

Основной принцип в стандартной библиотеке состоит в том, что «контейнеры» являются однородными; Стандарт C ++ не рассматривает такие вещи, как std::pair или std::tuple как контейнеры. (Я считаю, что график вводит в заблуждение, поскольку он рассматривает их как контейнеры.) Если вам нужен гетерогенный контейнер, вам придется использовать контейнер boost::variant или что-то в этом духе.

4 голосов
/ 18 октября 2011

std::pair и std::tuple вряд ли являются контейнерами C ++ .... так что нет, в STL нет гетерогенных контейнеров, потому что нет необходимости иметь их встроенными.

Есть несколькоПодходит для создания таких контейнеров.Подходы, которые я бы порекомендовал:

  • с использованием полиморфизма
  • с использованием типа варианта

Для полиморфизма вы можете выбрать BoostКонтейнер указателя library.

boost::ptr_vector<Base> vec;
vec.push_back(new Derived);
vec.push_back(new Derived2);

Он имитирует контейнеры STL, но предоставляет функции, ориентированные на полиморфизм:

  • Доступ к элементам как Base&
  • Автоматическая обработка памяти
  • Специфическое поведение копирования (с использованием new_clone методов)
  • Синтаксический сахар: дано boost::ptr_vector<Base>::iterator it;, *it является Base&

Если ваши типы не связаны, другой возможностью является использование Boost Variant .По сути, вариант похож на:

enum { Type1, Type2, ... } _type;
union {
  SomeType1 _1;
  SomeType2 _2;
  ...
} _u;

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

Он также предоставляет средства, такие как static_visitor, который эквивалентен переключателю типа, и выдаст ошибку компиляции, если один извозможные состояния не посещаются.

3 голосов
/ 12 мая 2017

Библиотека, которая еще не принята в Boost. Но то, что было предложено для включения, нацелено на это:

http://rawgit.com/joaquintides/poly_collection/website/doc/html/index.html

Он предоставляет хороший класс с именем any_collection, который позволяет иметь гетерогенный контейнер с помощью boost :: type_erasure :: any: http://rawgit.com/joaquintides/poly_collection/website/doc/html/poly_collection/tutorial.html#poly_collection.tutorial.basics.boost_any_collection

В противном случае в C ++ 17 есть простой способ реализовать это: https://gieseanw.wordpress.com/2017/05/03/a-true-heterogeneous-container-in-c/

Цитирую пример вышеупомянутой статьи:

namespace andyg{
struct heterogeneous_container{
private:
    template<class T>
    static std::unordered_map<const heterogeneous_container*, std::vector<T>> items;
public:
    template <class T>
    void push_back(const T& _t)
    {
        items<T>[this].push_back(_t);
    }
};

// storage for our static members
template<class T>
std::unordered_map<const heterogeneous_container*, std::vector<T>> heterogeneous_container::items;
} // andyg namespace

Тогда легко использовать:

andyg::heterogeneous_container c;
c.push_back(1);
c.push_back(2.f);
c.push_back('c');
struct LocalStruct{};
c.push_back(LocalStruct{});

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

2 голосов
/ 18 октября 2011

Гетерогенные контейнеры фиксированного размера (например, std::tuple) требуют, чтобы типы были известны во время компиляции. Если вы хотите создать гетерогенный контейнер переменного размера, просто создайте std::vector<std::tuple<T1,T2,...,TN>>.

Если вам нужен гетерогенный контейнер, в котором типы неизвестны во время компиляции (будь то переменный или фиксированный размер), вам придется хранить указатели (или умные указатели) на базовый тип, известный во время компиляции, или в качестве альтернативы рассмотрим что-то вроде контейнера boost::any. STL напрямую не предоставляет такой контейнер фиксированного или переменного размера с разнородными элементами, определенными во время выполнения.

1 голос
/ 18 октября 2011

Если элемент, который вы сохраняете, будет boost::any или boost::variant, то вы можете косвенно хранить разнородные данные.

...