Poiché il tipo di una colonna è un parametro di modello, stai modellando il tipo di colonna all'interno del sistema di tipi C ++. Questo è buono. A Column<int>
e Column<std::string>
sono tipi diversi. Se esistono alcune proprietà comuni a tutti i tipi di colonna (ad esempio che una colonna ha un nome), è possibile estrarle in una classe base in modo che sia possibile accedere a queste operazioni comuni tramite un tipo comune. Tuttavia, in questa base non possono esistere operazioni specifiche del tipo come get()
o sum()
, e devono essere parte del Column<T>
con template.
Se hai un tipo di tabella che ha colonne di tipi diversi , è chiaramente impensabile forzare questi ad avere lo stesso tipo dato che perderesti necessariamente l'accesso al parametro template ("cancellazione tipo “). Invece, abbraccia i diversi tipi e rendi anche il tuo Table
strongmente digitato. Un contenitore come std::tuple<T...>
può aiutarti qui.
Se hai bisogno di accedere alle parti indipendenti del tipo di colonna, puoi sempre ottenere un puntatore alla colonna che può essere utilizzata come tipo di base.
Uno schizzo con C ++ 14 (C ++ 11 richiederebbe di implementare un paio di funzioni pratiche da soli, ma ha std::tuple
e pacchetti di parametri template):
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)>{});
}
};
Potremmo quindi stampare il nome di tutte le colonne:
for (const auto& colBase : table.columns_vec())
std::cout << "column " << colBase->name() << "\n";
senza dover gestire separatamente ogni tipo di colonna.
( demo eseguibile su ideone )
Solo i modelli ti daranno la sicurezza del tipo che ottieni un int
da una colonna intera. Al contrario, i tipi di unioni / varianti richiedono l'uso del codice per ricordare tutti i tipi possibili (con il modello, il controllo del tipo impone che gestiamo tutto). Con la sottotipizzazione, non possiamo avere operazioni specifiche di tipo di colonna che condividono un'implementazione. Cioè un metodo int IntColumn::get(std::size_t i)
e un metodo correlato const std::string& StringColumn::get(std::size_t i)
potrebbero apparire come se avessero un'interfaccia comune, ma ciò sarebbe solo accidentale e non può essere applicato. In particolare, qualsiasi combinazione di metodi e modelli virtuali in C ++ diventa molto brutta, molto veloce.
Lo svantaggio dei modelli è che ti verrà richiesto di scrivere scrupolosamente codice generico e dovrai eseguire metaprogrammazione del modello. Se eseguiti correttamente, i risultati possono avere un'incredibile usabilità, ma l'implementazione sarebbe avanzata in C ++. Se il tuo progetto è progettato per essere gestito da programmatori meno avanzati (che saranno confusi come lo sarò quando ripenso a questo codice tra un paio di mesi), allora potrebbe essere più sensato evitare una soluzione così "intelligente" nonostante i suoi vantaggi e usi modelli OOP più tradizionali che ti danno una struttura simile, ma potrebbe richiedere un paio di% distatic_cast
s per funzionare.