In realtà è necessario implementare qualsiasi tipo di struttura di dati che alloca più memoria di quella minima richiesta per il numero di elementi inseriti (ovvero qualsiasi cosa diversa da una struttura collegata che alloca un nodo alla volta).
Contenitori Prendere piace unordered_map
, vector
o deque
. Tutti questi allocano più memoria di quanto è minimamente necessario per gli elementi che hai inserito finora per evitare di richiedere un'allocazione di heap per ogni singolo inserimento. Usiamo vector
come esempio più semplice.
Quando lo fai:
vector<Foo> vec;
// Allocate memory for a thousand Foos:
vec.reserve(1000);
... che in realtà non costruisce un migliaio di Foos. Alloca semplicemente / riserva la memoria per loro. Se vector
non utilizzassi il posizionamento nuovo qui, sarebbe una costruzione predefinita in Foos
tutto il luogo, oltre a dover invocare i loro distruttori anche per elementi che non hai mai nemmeno inserito in primo luogo.
Allocation! = Construction, Freeing! = Destruction
In generale, per implementare molte strutture di dati come sopra, non è possibile trattare l'allocazione della memoria e la costruzione di elementi come una cosa indivisibile, e allo stesso modo non è possibile trattare la liberazione della memoria e la distruzione di elementi come una cosa indivisibile.
Deve esserci una separazione tra queste idee per evitare costruttori e distruttori inutilmente invocati a destra e sinistra, ed è per questo che la libreria standard separa l'idea di std::allocator
(che non costruisce o distrugge elementi quando alloca / libera memoria *) da i contenitori che lo usano che costruiscono manualmente gli elementi usando il posizionamento nuovo e distruggono manualmente gli elementi usando invocazioni esplicite dei distruttori.
- Odio il design,
std::allocator
ma è un argomento diverso che eviterò di discutere. :-D
Quindi, comunque, tendo ad usarlo molto poiché ho scritto un certo numero di contenitori C ++ conformi agli standard per scopi generici che non potevano essere costruiti in termini di quelli esistenti. Tra questi c'è una piccola implementazione vettoriale che ho costruito un paio di decenni fa per evitare allocazioni di heap nei casi più comuni e un trie efficiente in termini di memoria (che non alloca un nodo alla volta). In entrambi i casi non sono stato in grado di implementarli utilizzando i contenitori esistenti, quindi ho dovuto utilizzare placement new
per evitare costruttori e distruttori superflui su cose inutili a destra e a sinistra.
Naturalmente, se mai lavori con allocatori personalizzati per allocare oggetti singolarmente, come un elenco gratuito, allora in genere vorresti anche usarli placement new
, come questo (esempio di base che non si preoccupa della sicurezza delle eccezioni o RAII):
Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);