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

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

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

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

- Сортировка против несортированных данных

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

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

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

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

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

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

8 ответов

Решение

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

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

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

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

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

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

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

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

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

Для Полиморфизма вы можете проверить библиотеку Boost Pointer Container.

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

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

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

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

Я бы указал вам на эту библиотеку. Он реализован как настоящий гетерогенный контейнер 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));

Отметил все ответы, в которых упоминается boost::variant.

Но начиная с C++17 сам стандарт предоставляетstd::variant. Это типобезопасное объединение.

          //A vector holds multiple types as declared in std::variant.
    std::vector<std::variant<int, double, std::string>> multiTypeVector;

    //Add eleemnts.
    multiTypeVector.push_back(8);
    multiTypeVector.push_back(8.0);
    multiTypeVector.push_back("test");
    multiTypeVector.push_back("test more");

    //Testing :: print elements.
    for (auto element : multiTypeVector)
        std::visit([](auto arg) {std::cout << arg << std::endl; }, element);
Другие вопросы по тегам