Pergunta
Eu estou no processo de criação de uma classe que armazena metadados sobre uma fonte de dados particular. Os metadados são estruturados em uma árvore, muito semelhante à forma como XML é estruturado. Os valores de metadados pode ser inteiro, decimal ou valores de cadeia.
Estou curioso para saber se há uma boa maneira em C ++ para armazenar dados variantes para uma situação como esta. Eu gostaria que a variante de usar bibliotecas padrão, então eu estou evitando a COM, Ole, e SQL VARIANT tipos que estão disponíveis.
O meu atual solução é algo como isto:
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;
};
A classe metavalue tem várias funções GET para obter o valor variante atualmente armazenado, mas acaba tornando cada consulta para um valor um grande bloco de if / else if para descobrir qual valor que eu estou procurando.
Eu também explorou armazenar o valor como apenas uma corda, e realizando conversões para obter diferentes tipos de variantes, mas, tanto quanto eu vi isso leva a um monte de análise de cadeia interna e tratamento de erros que não é bonita, abre-se uma grande lata velha de questões de precisão e de perda de dados com valores de ponto flutuante, e ainda não elimina a consulta if / else if problema indicado acima.
Tem alguém implementado ou visto algo que é mais limpo para usar um tipo de C ++ dados variante usando bibliotecas padrão?
Solução
Como de C ++ 17 anos, há std::variant
.
Se você não pode usar isso ainda, você pode querer Boost.Variant . Um semelhante, mas distinta, tipo de polimorfismo de modelagem é fornecido por std::any
(e , pré-C ++ 17, Boost.Any ).
Assim como um ponteiro adicional, você pode olhar para “ digite apagamento ”.
Outras dicas
Enquanto a resposta de Konrad (utilizando uma solução padronizada existente) é certamente preferível escrever a sua própria versão bug-propensa, a variante impulso tem algumas despesas gerais, especialmente na construção cópia e memória.
Uma abordagem comum personalizado é o seguinte padrão de fábrica modificado:
- criar uma interface de base de dados para um objecto genérico que também engloba o tipo de objecto (quer como uma enumeração), ou utilizando 'typeid' (preferível).
- Agora implementar a interface usando uma classe de modelo
Derived
. - Criar uma classe de fábrica com uma função
create
templateized com a assinatura:
template <typename _T> Base * Factory::create ();
Isso cria internamente um objeto Derived<_T>
na pilha, e retuns um ponteiro dinâmica elenco. Especialize isso para cada classe que você deseja implementar.
Finalmente, definir um invólucro Variant
que contém esse ponteiro Base *
e define modelo get e funções definidas. funções de serviços públicos como getType()
, isEmpty()
, atribuição e operadores de igualdade, etc pode ser adequadamente implementado aqui.
Dependendo das funções de serviço público e a implementação da fábrica, aulas apoiadas terão de suportar algumas funções básicas, como a atribuição ou copiar construção.
Você também pode ir para baixo para uma solução mais C-ish, que teria um void * do tamanho de um casal em seu sistema, além de um enum para que tipo você está usando. É razoavelmente limpo, mas definitivamente uma solução para alguém que se sente totalmente confortável com os bytes brutos do sistema.
C ++ 17 agora tem std::variant
que é exatamente o que você está procurando.
Embora a pergunta tinha sido respondida por um longo tempo, para o registro eu gostaria de mencionar que QVariant também faz isso.