Contenedores heterogéneos en C++
stl containers (6)
El principio básico en la biblioteca estándar es que los "contenedores" son homogéneos; el estándar de C ++ no considera que cosas como std::pair
o std::tuple
sean contenedores. (Considero que el gráfico es engañoso, ya que los considera contenedores). Si necesita un contenedor heterogéneo, debería usar un contenedor de boost::variant
, o algo similar.
Vi este lindo gráfico que clasifica qué contenedor STL sería adecuado en función de los diferentes requisitos de datos, tales como:
- Tamaño fijo Vs Tamaño variable
- Datos del mismo tipo Vs diferentes tipos
- Ordenado Vs datos no ordenados
- Acceso aleatorio V secuencial
http://plasmahh.projectiwear.org/cce_clean.svg
Observo en esa imagen, que C ++ STL no hay contenedor que sea
- Tamaño variable
- Heterogéneo (datos de diferentes tipos).
¿C ++ no tiene algo para esto?
PD: Puede haber muchas permutaciones hechas de las diferentes propiedades de los contenedores y muchas otras también podrían no proporcionarse en STL.
En general, los Contenedores C ++ están diseñados para contener objetos de un solo tipo utilizando plantillas. Si desea diferentes tipos que se derivan de un tipo, puede almacenar un contenedor de punteros (supongo que también podría tener un contenedor de vacío * a cualquier cosa ...), por ejemplo, std :: vector <MyBaseType *>.
Si desea tipos completamente independientes, puede almacenar objetos que puedan hacer referencia de forma segura a esos otros tipos, como boost :: any.
http://www.boost.org/doc/libs/1_47_0/doc/html/any.html
Algunos ejemplos del sitio de impulso:
#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 es similar, pero usted especifica todos los tipos permitidos, en lugar de permitir cualquier tipo en su contenedor.
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
Los contenedores heterogéneos de tamaño fijo (como std::tuple
requieren que los tipos se conozcan en tiempo de compilación. Si desea crear un contenedor heterogéneo de tamaño variable, simplemente std::vector<std::tuple<T1,T2,...,TN>>
un std::vector<std::tuple<T1,T2,...,TN>>
.
Si desea un contenedor heterogéneo donde los tipos no se conocen en tiempo de compilación (ya sea que sea de tamaño variable o fijo) tendrá que almacenar punteros (o punteros inteligentes) a un tipo base conocido en tiempo de compilación o, alternativamente, considerar algo como un contenedor de boost::any
. El STL no proporciona directamente dicho contenedor en tamaño fijo o variable con elementos heterogéneos determinados en tiempo de ejecución.
Si el elemento que almacena sería un boost::any
o boost::variant
entonces puede almacenar indirectamente datos heterogéneos.
Una biblioteca que todavía no se acepta en Boost. Pero lo que se propuso para la inclusión está dirigido a esto:
http://rawgit.com/joaquintides/poly_collection/website/doc/html/index.html
Proporciona una buena clase llamada any_collection que permite tener un contenedor heterogéneo a través de boost :: type_erasure :: any: http://rawgit.com/joaquintides/poly_collection/website/doc/html/poly_collection/tutorial.html#poly_collection.tutorial.basics.boost_any_collection
De lo contrario, en C ++ 17 hay una forma sencilla de implementar esto: https://gieseanw.wordpress.com/2017/05/03/a-true-heterogeneous-container-in-c/
Citando el ejemplo del artículo antes mencionado:
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
Luego utilizable fácilmente:
andyg::heterogeneous_container c;
c.push_back(1);
c.push_back(2.f);
c.push_back(''c'');
struct LocalStruct{};
c.push_back(LocalStruct{});
El autor afirma que es una implementación de juguete, pero creo que esta es una forma realmente inteligente de implementarlo, y tiene una ventaja de simplicidad sobre poly_collection o un vector de variantes.
std::pair
y std::tuple
son apenas contenedores C ++ ... así que no, no hay contenedores heterogéneos en el STL, porque no es necesario tenerlos incorporados.
Hay varios enfoques para crear tales contenedores. Los enfoques que recomendaría son:
- usando polimorfismo
- usando un tipo de variante
Para el polimorfismo, puede consultar la biblioteca Boost Pointer Container .
boost::ptr_vector<Base> vec;
vec.push_back(new Derived);
vec.push_back(new Derived2);
Imita los contenedores STL, pero proporciona funcionalidades orientadas hacia el polimorfismo:
- Elementos de acceso como
Base&
- Manejo automático de la memoria
- Comportamiento de copia específico (utilizando métodos
new_clone
) - Azúcar sintáctico:
boost::ptr_vector<Base>::iterator it;
dadoboost::ptr_vector<Base>::iterator it;
,*it
es unaBase&
Si sus tipos no están relacionados, la otra posibilidad es usar el http://www.boost.org/doc/libs/1_47_0/doc/html/variant.html . Básicamente, una variante es similar a:
enum { Type1, Type2, ... } _type;
union {
SomeType1 _1;
SomeType2 _2;
...
} _u;
Por supuesto, dado que es un impulso, proporciona garantías específicas para garantizar que solo pueda acceder al miembro de la unión que está actualmente activo y elimina la restricción de clases con constructores / destructores que no pueden utilizarse en las uniones tradicionales.
También proporciona instalaciones, como el static_visitor
, que es el equivalente a un interruptor en el tipo, y hará que el error de compilación salga si no se visita uno de los estados posibles.