2010-06-23 19 views
31

In C le funzioni di gestione memoria standard sono malloc(), realloc() e free(). Tuttavia, gli allocatori stdlib C++ ne hanno solo due paralleli: non esiste una funzione di riallocazione. Naturalmente, non sarebbe possibile fare esattamente lo stesso realloc(), perché copiare semplicemente la memoria non è appropriato per i tipi non aggregati. Ma ci sarebbe un problema, per esempio, questa funzione:Perché non ci sono funzionalità di riallocazione negli allocatori C++?

bool reallocate (pointer ptr, size_type num_now, size_type num_requested); 

dove

  • ptr è precedentemente assegnati con lo stesso allocatore per num_now oggetti;
  • num_requested> = num_now;

e semantica come segue:

  • se allocatore può espandersi in blocco di memoria a ptr dal formato per num_now oggetti num_requested oggetti, lo fa (lasciando memoria aggiuntiva inizializzata) e restituisce true;
  • altrimenti non fa nulla e restituisce false.

Concesso, questo non è molto semplice, ma gli allocatori, come comprendo, sono principalmente pensati per il contenitore e il codice dei contenitori è in genere già complicato.

Dato tale funzione, std::vector, per esempio, potrebbe crescere come segue (pseudocodice):

if (allocator.reallocate (buffer, capacity, new_capacity)) 
    capacity = new_capacity;  // That's all we need to do 
else 
    ... // Do the standard reallocation by using a different buffer, 
     // copying data and freeing the current one 

Allocators che sono incapaci di cambiare la dimensione della memoria del tutto potrebbe semplicemente attuare tale funzione incondizionato return false;.

Ci sono così poche implementazioni di allocatore capaci di riallocazione che non ne vale la pena disturbare? O ci sono dei problemi che ho trascurato?

+13

+1, questa è una domanda che mi ha sempre infastidito. –

+0

Il punto di vista di Stroustrup su questa cosa: http://www2.research.att.com/~bs/bs_faq2.html#renew; esso delega il problema al funzionamento interno del vettore, ma non dice perché non esiste un meccanismo come "rinnovare" per semplificare la crescita dell'array. –

+1

Non c'è nulla che impedisca a 'std :: vector' di farlo in alcuni casi (ad esempio, sa che sta usando l'allocatore standard). La libreria standard può utilizzare la conoscenza del sistema sottostante. – KeithB

risposta

17

Da: http://www.sgi.com/tech/stl/alloc.html

Questa è probabilmente la decisione design più discutibile. Sarebbe stato probabilmente un po 'più utile a fornire una versione di riallocata che o modificato la dimensione dell'oggetto esistente senza copiare o restituito NULL. Ciò lo avrebbe reso direttamente utile per gli oggetti con i costruttori copia . Sarebbe anche potuto evitato la copia non necessaria nei casi in cui l'oggetto originale non era stato completamente compilati.

Purtroppo, questo sarebbe uso vietato di realloc dalla libreria C . Ciò a sua volta avrebbe aggiunto la complessità a molte implementazioni di allocatore e avrebbe reso più difficile l'interazione con gli strumenti di memoria debugging . Così abbiamo deciso di contro questa alternativa.

+2

Penso che sia un peccato non aver aggiunto almeno un metodo di riallocazione all'interfaccia di Allocator. Avrebbe potuto essere implementato per liberare solo un blocco esistente e assegnarne uno nuovo, ma avrebbe dato la possibilità di implementarne uno nuovo in seguito senza dover ripetere tutto il codice usando l'interfaccia di Allocator. – stinky472

1

Immagino che questa sia una delle cose in cui dio ha sbagliato, ma ero troppo pigro per scrivere al comitato degli standard.

Ci sarebbe dovuto essere un realloc per le allocazioni di array:

p = renew(p) [128];

o qualcosa di simile.

+0

Se si usano i vettori anziché gli array, c'è '.reserve()'. Perché aggiungere nuove funzionalità per gli array quando i vettori sono generalmente migliori? –

+1

@DavidThornley, i vettori sotto il cofano devono passare attraverso l'interfaccia allocatore. Quindi sembra che un vettore non possa liberare la memoria inutilizzata nel modo più efficiente possibile. (Ma penso/spero di perdere qualcosa qui!) –

+1

@DavidThornley - esattamente quello che ha detto Aaron. _Everyone_ dice "usa' vector' "- ma stanno parlando al livello sbagliato di astrazione! 'vector' stesso deve essere costruito (e in effetti è costruito su) le routine di allocazione di livello inferiore che consentono di ottenere la memoria non inizializzata e così via. Se quelle routine non offrono una funzione di "riallocazione" di basso livello, certamente 'vector' non può neanche. Ovviamente, può offrire 'ridimensiona' e' riservatore' e tutto il resto, ma sotto le coperte si stanno solo assegnando nuovi blocchi e copiando le cose. Niente come espandere un blocco esistente. – BeeOnRope

3

A causa della natura orientata agli oggetti del C++ e dell'inclusione dei vari tipi di contenitori standard, penso che sia semplicemente che ci sia meno attenzione sulla gestione della memoria di direzione rispetto a C. Sono d'accordo che ci sono casi che un realloc () sarebbe utile, ma la pressione per porvi rimedio è minima, poiché quasi tutte le funzionalità risultanti possono essere acquisite utilizzando invece i contenitori.

+1

Non sono sicuro di essere d'accordo, ci hanno pensato meno a causa di OOP. Il posizionamento nuovo è un esempio di una funzione specifica per la gestione della memoria degli oggetti. –

+0

Non sto dicendo che ci abbiano pensato meno, solo che la sintassi effettiva è stata progettata per porre più enfasi su OOP che sulla gestione diretta della memoria nei casi in cui le due opzioni sono diverse. Il nuovo posizionamento è un perfetto esempio di dove i due sono usati insieme dal programmatore piuttosto che uno che sostituisce l'altro. – tlayton

+0

Il posizionamento effettivo è l'apparato che * abilita * la gestione della memoria personalizzata altrimenti molto dura (in particolare i pool di memoria) all'interno delle strutture C++. Lo considero un dispositivo che * incoraggia * la personalizzazione della gestione della memoria con le librerie di codici C++. –

8

Quello che stai chiedendo è essenzialmente ciò che fa vector::reserve. Senza spostare la semantica per gli oggetti, non c'è modo di riallocare la memoria e spostare gli oggetti senza fare copia e distruggere.

+0

Un buon caso per tali funzionalità sarebbe costituito da contenitori sparsi. Utilizzando i vettori, specialmente con la memoria preallata, in essi sarebbe completamente vanificato il loro scopo (la sparsità è destinata a conservare la memoria). – doublep

+1

@doublep: se si desidera disporre di contenitori sparsi, né gli array allocati (dinamicamente) né i vettori sono ciò che si desidera. –

+0

@ Martin York: beh, ad esempio, la libreria di Google Sparsehash utilizza matrici allocate dinamicamente e ottiene risultati molto interessanti. – doublep

11

Questo è in realtà un difetto di progettazione che Alexandrescu sottolinea con i ripartitori standard (non operatore new []/delete [] ma ciò che erano in origine i ripartitori STL utilizzati per implementare std :: vector, per esempio).

Un realloc può verificarsi molto più velocemente di un malloc, memcpy e gratuito. Tuttavia, mentre il blocco di memoria reale può essere ridimensionato, può anche spostare la memoria in una nuova posizione. In quest'ultimo caso, se il blocco di memoria è costituito da non POD, tutti gli oggetti dovranno essere distrutti e copiati dopo il realloc.

La cosa principale che la libreria standard deve contenere come una possibilità è una funzione di riallocazione come parte dell'interfaccia pubblica dello standard di allocatore. Una classe come std :: vector potrebbe certamente usarla anche se l'implementazione di default è di malloc il blocco di nuova dimensione e libera quello vecchio. Dovrebbe essere una funzione che sia in grado di distruggere e copiare gli oggetti in memoria, tuttavia, se non lo fa, non può trattare la memoria in modo opaco. C'è un po 'di complessità in gioco e richiederebbe un po' di lavoro sui template che potrebbe essere il motivo per cui è stato omesso dalla libreria standard.

std :: vector < ...>: reserve non è sufficiente: si rivolge a un caso diverso in cui è possibile anticipare la dimensione del contenitore. Per gli elenchi di dimensioni veramente variabili, una soluzione di realloc potrebbe rendere i contenitori contigui come std :: vector molto più velocemente, specialmente se può trattare casi di realloc in cui il blocco di memoria è stato ridimensionato correttamente senza essere spostato, nel qual caso può omettere la copia di chiamata costruttori e distruttori per gli oggetti in memoria.

+1

+1 Un collegamento per il backup: http://www.stepanovpapers.com/notes.pdf –

Problemi correlati