Como o tipo de coluna é um parâmetro de modelo, você está modelando o tipo de coluna no sistema de tipos C ++. Isso é bom. Um Column<int>
e Column<std::string>
são tipos diferentes. Se houver algumas propriedades comuns a todos os tipos de colunas (por exemplo, se uma coluna tiver um nome), você poderá extraí-las em uma classe base para que essas operações comuns possam ser acessadas por meio de um tipo comum. No entanto, nenhuma operação específica de tipo, como get()
ou sum()
, pode existir nessa base e deve fazer parte do modelo Column<T>
.
Se você tem um tipo de tabela que tem colunas de tipos diferentes , claramente não é sensato forçá-los a ter o mesmo tipo, pois você perderia necessariamente o acesso ao parâmetro do modelo (“tipo apagar ”). Em vez disso, adote os diferentes tipos e também faça com que o seu Table
seja strongmente tipado. Um contêiner como std::tuple<T...>
pode ajudar aqui.
Se você precisar acessar as partes independentes do tipo coluna, poderá sempre obter um ponteiro para a coluna que pode ser usada como o tipo base.
Um esboço usando C ++ 14 (C ++ 11 exigiria que você mesmo implementasse algumas funções de conveniência, mas possui std::tuple
e pacotes de parâmetros de modelo):
class ColumnBase {
...
public:
std::string name() { … }
};
template<class T>
class Column : public ColumnBase {
std::vector<T> m_items;
...
};
template<class... T>
class Table {
std::tuple<Column<T>...> m_columns;
template<std::size_t... index>
std::vector<ColumnBase*> columns_vec_helper(std::index_sequence<index...>) {
return { (&std::get<index>(m_columns))... };
}
public:
std::vector<ColumnBase*> columns_vec() {
return columns_vec_helper(std::make_index_sequence<sizeof...(T)>{});
}
};
Poderíamos então imprimir o nome de todas as colunas:
for (const auto& colBase : table.columns_vec())
std::cout << "column " << colBase->name() << "\n";
sem ter que lidar com cada tipo de coluna separadamente.
( demonstração executável sobre o ideone )
Somente os modelos darão a você o tipo de segurança de que você obterá um int
de uma coluna inteira. Em contraste, os tipos de uniões / variantes requerem o código de uso para lembrar de todos os tipos possíveis (com o modelo, o verificador de tipos impõe que lidamos com tudo). Com a subtipagem, não podemos ter operações específicas do tipo coluna que compartilhem uma implementação. Ou seja um método int IntColumn::get(std::size_t i)
e um método relacionado const std::string& StringColumn::get(std::size_t i)
podem parecer que eles têm uma interface comum, mas isso seria apenas acidental e não pode ser aplicado. Em particular, qualquer combinação de métodos e modelos virtuais em C ++ fica muito feia, muito rápida.
A desvantagem dos modelos é que você precisará escrever cuidadosamente o código genérico e terá que fazer a metaprogramação do modelo. Quando feito corretamente, os resultados podem ter uma usabilidade incrível, mas a implementação seria avançada em C ++. Se o seu design é destinado a ser mantido por programadores menos avançados (o que será tão confuso quanto eu estarei quando voltar a esse código em alguns meses), então seria mais sensato evitar uma solução tão “inteligente”. apesar de seus benefícios e usar padrões OOP mais tradicionais que fornecem uma estrutura semelhante, mas podem exigir alguns static_cast
s para funcionar.