Гетерогенные контейнеры в C++
Я видел этот хороший рисунок, который классифицирует, какой контейнер STL подойдет на основе различных требований данных, таких как:
- фиксированный размер и переменный размер
- Данные одного типа и другого типа
- Сортировка против несортированных данных
- Последовательный и произвольный доступ
http://plasmahh.projectiwear.org/cce_clean.svg
Я заметил, что в C++ STL нет контейнера, который
- Размер переменной
- Гетерогенный (данные разных типов).
Разве в С ++ нет чего-то для этого?
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);