Mi chiedo se sia una buona idea racchiudere i contenitori C++ STL per mantenere la coerenza e poter scambiare l'implementazione senza modificare il codice client.Contenimento dei contenitori per mantenere la coerenza
Per esempio, in un progetto che usiamo CamelCase per la denominazione classi e funzioni membro (Foo :: DoSomething()), mi sarebbe avvolgere std :: elenco in una classe come questa:
template<typename T>
class List
{
public:
typedef std::list<T>::iterator Iterator;
typedef std::list<T>::const_iterator ConstIterator;
// more typedefs for other types.
List() {}
List(const List& rhs) : _list(rhs._list) {}
List& operator=(const List& rhs)
{
_list = rhs._list;
}
T& Front()
{
return _list.front();
}
const T& Front() const
{
return _list.front();
}
void PushFront(const T& x)
{
_list.push_front(x);
}
void PopFront()
{
_list.pop_front();
}
// replace all other member function of std::list.
private:
std::list<T> _list;
};
Poi ho sarebbe in grado di scrivere qualcosa del genere:
typedef uint32_t U32;
List<U32> l;
l.PushBack(5);
l.PushBack(4);
l.PopBack();
l.PushBack(7);
for (List<U32>::Iterator it = l.Begin(); it != l.End(); ++it) {
std::cout << *it << std::endl;
}
// ...
credo che la maggior parte dei moderni compliers C++ possono ottimizzare via l'indirezione più facilmente, e credo che questo metodo ha alcuni vantaggi come:
Posso estendere facilmente la funzionalità della classe List. Per esempio, io voglio una funzione di scorciatoia che ordina l'elenco e quindi chiamare unico(), posso estenderlo con l'aggiunta di una funzione membro:
template<typename T>
void List<T>::SortUnique()
{
_list.sort();
_list.unique();
}
Inoltre, posso scambiare l'implementazione sottostante (se necessario), senza alcuna cambia il codice che usano List fintanto che il comportamento è lo stesso. Ci sono anche altri vantaggi, perché mantiene la coerenza delle convenzioni di denominazione in un progetto, in modo che non hanno push_back() per STL e pushback() per le altre classi di tutto il progetto come:
std::list<MyObject> objects;
// insert some MyObject's.
while (!objects.empty()) {
objects.front().DoSomething();
objects.pop_front();
// Notice the inconsistency of naming conventions above.
}
// ...
Sono chiedendosi se questo approccio abbia qualche svantaggio maggiore (o minore) o se questo sia in realtà un metodo pratico.
Grazie.
MODIFICATO: Ok, grazie per le risposte finora. Penso di aver messo troppo a dire la coerenza nella domanda. In realtà le convenzioni di denominazione non sono la mia preoccupazione qui, dal momento che si può fornire una esattamente stessa interfaccia così:
template<typename T>
void List<T>::pop_back()
{
_list.pop_back();
}
Oppure si può anche rendere l'interfaccia di un'altra implementazione sembrano più STL quella che la maggior parte dei programmatori C++ sono già familiari con. Ma comunque, secondo me è più una cosa di stile e non così importante.
Quello che mi preoccupava è la coerenza per poter modificare facilmente i dettagli di implementazione. Uno stack può essere implementato in vari modi: un array e un indice superiore, un elenco collegato o anche un ibrido di entrambi, e tutti hanno la caratteristica LIFO di una struttura dati. Un albero binario di ricerca autoequilibrante può essere implementato anche con un albero AVL o un albero rosso-nero, ed entrambi hanno una complessità temporale media O (logn) per la ricerca, l'inserimento e l'eliminazione.
Quindi, se ho una libreria ad albero AVL e un'altra libreria ad albero rosso-nero con interfacce diverse, e io uso un albero AVL per memorizzare alcuni oggetti. Più tardi, ho capito (usando i profiler o qualsiasi altra cosa) che usare un albero rosso-nero avrebbe dato una spinta in termini di prestazioni, avrei dovuto andare in ogni parte dei file che usano gli alberi AVL e cambiare la classe, i nomi dei metodi e probabilmente l'argomento ordini alle sue controparti albero rosso-nero. Ci sono probabilmente anche alcuni scenari in cui la nuova classe non ha ancora una funzionalità equivalente scritta. Penso che possa anche introdurre dei bug sottili anche a causa delle differenze nell'implementazione, o che io commetta un errore.
Così che cosa ho cominciato a chiedermi che se vale la pena l'overhead a mantenere tale classe wrapper per nascondere i dettagli di implementazione e fornire un'interfaccia uniforme per diverse implementazioni:
template<typename T>
class AVLTree
{
// ...
Iterator Find(const T& val)
{
// Suppose the find function takes the value to be searched and an iterator
// where the search begins. It returns end() if val cannot be found.
return _avltree.find(val, _avltree.begin());
}
};
template<typename T>
class RBTree
{
// ...
Iterator Find(const T& val)
{
// Suppose the red-black tree implementation does not support a find function,
// so you have to iterate through all elements.
// It would be a poor tree anyway in my opinion, it's just an example.
auto it = _rbtree.begin(); // The iterator will iterate over the tree
// in an ordered manner.
while (it != _rbtree.end() && *it < val) {
++it;
}
if (*++it == val) {
return it;
} else {
return _rbtree.end();
}
}
};
Ora, devo solo assicurati che AVLTree :: Find() e RBTree :: Find() facciano esattamente la stessa cosa (es. prendi il valore da cercare, restituisci un iteratore all'elemento o End(), altrimenti). E poi, se voglio cambiare da un albero AVL ad un albero rosso-nero, tutto quello che devo fare è modificare la dichiarazione:
AVLTree<MyObject> objectTree;
AVLTree<MyObject>::Iterator it;
a:
RBTree<MyObject> objectTree;
RBTree<MyObject>::Iterator it;
e tutto il resto sarà lo stesso, mantenendo due classi.
Quanto sopra è solo un esempio di ciò che volevo archiviare con la classe wrapper. Forse questo tipo di scenario è troppo raro nel vero sviluppo e che mi preoccupo troppo, non ne ho idea. Ecco perché lo sto chiedendo.
Ho cambiato il titolo da "Wrapping STL Container" a "Wrapping Containers per mantenere la coerenza" per riflettere il problema in modo più accurato.
Grazie per il vostro tempo.
Pensare a questa idea mi sta facendo fisicamente male. –
Non vedo nulla di sbagliato nel farlo. ma vuoi davvero ??? – Arunmu
Quindi, per ottenere tutto questo, vuoi fare tutto questo solo a causa dei nomi dei nomi e scambiando l'implementazione? Non è possibile nominare il nome, perché in questo caso si potrebbero includere molte altre cose STL. – RvdK