Гетерогенные контейнеры в C++

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

-- Фиксированный размер против переменного размера

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

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

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

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

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

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

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

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


person goldenmean    schedule 18.10.2011    source источник
comment
На изображении видно, что многие контейнеры, такие как vector, queue, stack и т. д., отображаются как переменный размер в зависимости от их цвета. Хотя не уверен.   -  person iammilind    schedule 18.10.2011
comment
вы можете использовать вектор указателя на абстрактный базовый класс, даже если другие решения, вероятно, лучше   -  person Ruggero Turra    schedule 18.10.2011
comment
Этот рисунок относится не к STL, а к стандартной библиотеке C++11.   -  person PlasmaHH    schedule 18.10.2011


Ответы (7)


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

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

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

Несколько примеров с сайта Boost:

#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::variant аналогичен, но вы указываете все разрешенные типы, а не разрешаете какой-либо тип в своем контейнере.

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
person Fire Lancer    schedule 18.10.2011

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

person James Kanze    schedule 18.10.2011

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

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

  • используя полиморфизм
  • используя вариантный тип

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

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

person Matthieu M.    schedule 18.10.2011

Библиотека, которая еще не принята в 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< /а>

В противном случае в С++ 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 или вектором вариантов.

person daminetreg    schedule 12.05.2017
comment
poly_collection был принят для повышения через 10 дней после того, как вы написали это комментарий. - person Ilya Popov; 08.07.2017
comment
Я не понимаю, разве статическая переменная не может иметь пареметр шаблона только один раз, или у нее есть несколько экземпляров шаблона? Может ли кто-нибудь указать мне место, где я мог бы лучше понять такие вещи? - person The Floating Brain; 14.11.2017

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

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

person Clinton    schedule 18.10.2011
comment
std::tuple и std::pair являются неоднородными контейнерами, как и любой заданный UDT. Например, struct foo { int i; float f; std::string s; };, неоднородный контейнер. - person dalle; 18.10.2011
comment
+1, STL - это композиция. Нет необходимости заранее определять все возможные комбинации. - person MSalters; 18.10.2011
comment
@dalle: Не совсем, вы можете перебирать элементы кортежа. - person MSalters; 18.10.2011
comment
Я не уверен, как это комментарии к моему ответу. Я не говорил, что у std нет гетерогенных контейнеров, я просто сказал, что у него нет контейнеров неизвестного типа во время компиляции. - person Clinton; 19.10.2011

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

person dalle    schedule 18.10.2011

Я бы указал вам на эту библиотеку. Он реализован как настоящий гетерогенный контейнер https://github.com/hosseinmoein/DataFrame. использовать полиморфизм и, следовательно, хранить указатели. Он использует непрерывную память, как и std::vector.

Вы можете написать такой код

typedef StdDataFrame<unsigned long> MyDataFrame;

MyDataFrame                df;
std::vector<int>           intvec = { 1, 2, 3, 4, 5 };
std::vector<double>        dblvec = { 1.2345, 2.2345, 3.2345, 4.2345, 5.2345 };
std::vector<double>        dblvec2 = { 0.998, 0.3456, 0.056, 0.15678, 0.00345,
                                       0.923, 0.06743, 0.1 };
std::vector<std::string>   strvec = { "Insight", "John Dow", "Alakazam",
                                      "Persian Prince", "Bugs Bunny" };
std::vector<unsigned long> ulgvec = { 1UL, 2UL, 3UL, 4UL, 5UL, 8UL, 7UL, 6UL }
std::vector<unsigned long> xulgvec = ulgvec;

// This is only one way of loading data into a DataFrame instance. There are
// many different ways of doing it. Please see the documentation,
// or dataframe_tester.cc
int rc = df.load_data(std::move(ulgvec),  // Index column
                      std::make_pair("int_col", intvec),
                      std::make_pair("dbl_col", dblvec),
                      std::make_pair("dbl_col_2", dblvec2),
                      std::make_pair("str_col", strvec),
                      std::make_pair("ul_col", xulgvec));
person hmoein    schedule 02.01.2020
comment
Как правило, вам следует избегать публикации ответов, содержащих только ссылки. Рассмотрите возможность добавления фрагментов кода вместе с пояснениями. Если вы хотите просто поделиться ссылкой, ее следует добавить как комментарий к исходному вопросу, а не как ответ. - person sshashank124; 02.01.2020