Pregunta

Estoy en el proceso de crear una clase que almacene metadatos sobre una fuente de datos en particular. Los metadatos se estructuran en un árbol, muy similar a cómo se estructura XML. Los valores de metadatos pueden ser enteros, decimales o valores de cadena.

Tengo curiosidad por saber si hay una buena manera en C ++ para almacenar datos de variantes para una situación como esta. Me gustaría que la variante use bibliotecas estándar, por lo que estoy evitando los tipos VARIANT COM, Ole y SQL que están disponibles.

Mi solución actual se parece a esto:

enum MetaValueType
{
    MetaChar,
    MetaString,
    MetaShort,
    MetaInt,
    MetaFloat,
    MetaDouble
};

union MetaUnion
{
    char cValue;
    short sValue;
    int iValue;
    float fValue;
    double dValue;
};

class MetaValue
{
...
private:
    MetaValueType ValueType;
    std::string StringValue;
    MetaUnion VariantValue;
};

La clase MetaValue tiene varias funciones Get para obtener el valor de la variante almacenada actualmente, pero termina convirtiendo cada consulta de un valor en un gran bloque de sentencias if / else if para determinar qué valor estoy buscando.

También he explorado almacenar el valor solo como una cadena y realizar conversiones para obtener diferentes tipos de variantes, pero hasta donde he visto, esto lleva a un montón de análisis interno de cadenas y manejo de errores que no es bonito, abre una gran lata antigua de problemas de precisión y pérdida de datos con valores de punto flotante, y todavía no elimina la consulta si / si el problema se menciona anteriormente.

¿Alguien ha implementado o visto algo que sea más limpio de usar para un tipo de datos variante de C ++ usando bibliotecas estándar?

¿Fue útil?

Solución

A partir de C ++ 17, hay std :: variant .

Si aún no puede usar eso, es posible que desee Boost.Variant . std :: cualquier tipo similar, pero distinto, para modelar el polimorfismo. (y, pre-C ++ 17, Boost.Any ).

Como un puntero adicional, puede buscar " borrado de tipo ".

Otros consejos

Si bien la respuesta de Konrad (usar una solución estandarizada existente) es ciertamente preferible a escribir su propia versión propensa a errores, la variante boost tiene algunos gastos generales, especialmente en la construcción de copias y la memoria.

Un enfoque personalizado común es el siguiente patrón de fábrica modificado:

  1. Cree una interfaz Base para un objeto genérico que también encapsule el tipo de objeto (ya sea como una enumeración), o usando 'typeid' (preferible).
  2. Ahora implemente la interfaz usando una plantilla Derived class.
  3. Cree una clase de fábrica con una función de create con plantilla con firma:

template < typename _T > Base * Factory :: create ();

Esto crea internamente un objeto Derivado < _T > en el montón, y vuelve a mostrar un puntero de conversión dinámico. Especialízalo para cada clase que quieras implementar.

Finalmente, defina una envoltura de Variant que contenga este puntero de Base * y defina las funciones de obtención y configuración de la plantilla. Las funciones de utilidad como getType () , isEmpty () , los operadores de asignación e igualdad, etc. pueden implementarse adecuadamente aquí.

Dependiendo de las funciones de utilidad y la implementación de fábrica, las clases compatibles deberán admitir algunas funciones básicas como asignación o construcción de copia.

También puede ir a una solución más C-ish, que tendría un vacío * del tamaño de un doble en su sistema, más una enumeración para el tipo que está usando. Es razonablemente limpio, pero definitivamente es una solución para alguien que se siente completamente cómodo con los bytes en bruto del sistema.

C ++ 17 ahora tiene std :: variant que es exactamente lo que estás buscando.

std :: variant

Aunque la pregunta había sido respondida durante mucho tiempo, me gustaría mencionar que QVariant también hace esto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top