Devo memorizzare interi oggetti o puntatori a oggetti in contenitori?


162

Progettare un nuovo sistema da zero. Userò l'STL per memorizzare elenchi e mappe di alcuni oggetti longevo.

Domanda: Devo assicurarmi che i miei oggetti abbiano costruttori di copie e archiviare copie di oggetti nei miei contenitori STL, o è generalmente meglio gestire da solo la vita e l'ambito e archiviare i puntatori a quegli oggetti nei miei contenitori STL?

Mi rendo conto che questo è in qualche modo breve nei dettagli, ma sto cercando la risposta "teorica" ​​migliore se esiste, poiché so che entrambe queste soluzioni sono possibili.

Due svantaggi molto evidenti nel giocare con i puntatori: 1) Devo gestire da solo l'allocazione / deallocazione di questi oggetti in un ambito che va oltre la STL. 2) Non riesco a creare un oggetto temp nello stack e aggiungerlo ai miei contenitori.

C'è qualcos'altro che mi manca?


36
Dio, adoro questo sito, questa è la domanda ESATTA che stavo pensando oggi ... grazie per aver fatto il lavoro di chiedermelo :-)
eviljack

2
un'altra cosa interessante è che dovremmo verificare se il puntatore è stato effettivamente aggiunto alla raccolta e in caso contrario dovremmo probabilmente chiamare delete per evitare perdite di memoria ... if ((set.insert (pointer)). second = false) {delete pointer;}
javapowered l'

Risposte:


68

Dal momento che le persone stanno intervenendo sull'efficacia dell'uso dei puntatori.

Se stai pensando di utilizzare uno std :: vector e se gli aggiornamenti sono pochi e spesso esegui l'iterazione della tua raccolta ed è un tipo non polimorfico, la memorizzazione di oggetti "copie" sarà più efficace poiché otterrai una migliore località di riferimento.

Otoh, se gli aggiornamenti sono comuni, la memorizzazione dei puntatori farà risparmiare i costi di copia / trasferimento.


7
In termini di localizzazione della cache, l'archiviazione dei puntatori nel vettore può essere efficace se utilizzata insieme a un allocatore personalizzato per le punte. L'allocatore personalizzato deve occuparsi della località della cache, ad esempio utilizzando il posizionamento new (vedi en.wikipedia.org/wiki/Placement_syntax#Custom_allocators ).
Amit

47

Questo dipende davvero dalla tua situazione.

Se i tuoi oggetti sono piccoli e fare una copia dell'oggetto è leggero, la memorizzazione dei dati all'interno di un contenitore stl è semplice e facile da gestire secondo me perché non devi preoccuparti della gestione della vita.

Se gli oggetti sono di grandi dimensioni e avere un costruttore predefinito non ha senso o se le copie degli oggetti sono costose, probabilmente è la memorizzazione con i puntatori.

Se decidi di utilizzare i puntatori agli oggetti, dai un'occhiata alla libreria di contenitori Boost Pointer . Questa libreria boost avvolge tutti i contenitori STL per l'uso con oggetti allocati dinamicamente.

Ogni contenitore puntatore (ad esempio ptr_vector) diventa proprietario di un oggetto quando viene aggiunto al contenitore e gestisce la durata di tali oggetti. Puoi anche accedere a tutti gli elementi in un contenitore ptr_ per riferimento. Questo ti permette di fare cose del genere

class BigExpensive { ... }

// create a pointer vector
ptr_vector<BigExpensive> bigVector;
bigVector.push_back( new BigExpensive( "Lexus", 57700 ) );
bigVector.push_back( new BigExpensive( "House", 15000000 );

// get a reference to the first element
MyClass& expensiveItem = bigList[0];
expensiveItem.sell();

Queste classi avvolgono i contenitori STL e funzionano con tutti gli algoritmi STL, il che è davvero utile.

Esistono anche funzioni per trasferire la proprietà di un puntatore nel contenitore al chiamante (tramite la funzione di rilascio nella maggior parte dei contenitori).


38

Se stai memorizzando oggetti polimporici devi sempre usare una raccolta di puntatori di classe base.

Cioè, se prevedi di memorizzare diversi tipi derivati ​​nella tua raccolta, devi conservare i puntatori o farti mangiare dal demone.


1
Ho adorato il demone affettato!
idichekop,

22

Mi dispiace saltare tra 3 anni dopo l'evento, ma una nota di avvertimento qui ...

Nel mio ultimo grande progetto, la mia struttura centrale di dati era un insieme di oggetti abbastanza semplici. Circa un anno dopo l'inizio del progetto, quando i requisiti si sono evoluti, mi sono reso conto che l'oggetto doveva effettivamente essere polimorfico. Ci sono volute alcune settimane di chirurgia cerebrale difficile e sgradevole per sistemare la struttura dei dati come un insieme di indicatori di classe base e per gestire tutto il danno collaterale nell'archiviazione, nel lancio degli oggetti e così via. Mi ci sono voluti un paio di mesi per convincermi che il nuovo codice stava funzionando. Per inciso, questo mi ha fatto riflettere intensamente su quanto sia ben progettato il modello a oggetti di C ++.

Nel mio attuale grande progetto, la mia struttura centrale di dati è un insieme di oggetti abbastanza semplici. Circa un anno dopo l'inizio del progetto (che sembra essere oggi), mi sono reso conto che l'oggetto in realtà deve essere polimorfico. Torna alla rete, ha trovato questo thread e ha trovato il link di Nick alla libreria contenitore puntatore Boost. Questo è esattamente quello che ho dovuto scrivere l'ultima volta per sistemare tutto, quindi questa volta ci proverò.

La morale, per me, comunque: se le tue specifiche non sono espresse al 100% in pietra, cerca suggerimenti e potresti potenzialmente risparmiare molto lavoro in seguito.


Le specifiche non vengono mai impostate sulla pietra. Non penso che ciò significhi che dovresti usare esclusivamente i contenitori di puntatori, anche se i contenitori di puntatori Boost sembrano rendere questa opzione molto più attraente. Sono scettico sul fatto che è necessario rivedere l'intero programma tutto in una volta se si decide che un contenitore di oggetti deve essere convertito in un contenitore puntatore. Questo potrebbe essere il caso di alcuni progetti. In tal caso, è un design fragile. In tal caso, non incolpare il problema sulla "debolezza" dei contenitori di oggetti.
codice alleato

Avresti potuto lasciare l'elemento nel vettore con semantica di valore e fare il comportamento polimorfico all'interno.
Billy ONeal,

19

Perché non ottenere il meglio da entrambi i mondi: crea un contenitore di puntatori intelligenti (come boost::shared_ptro std::shared_ptr). Non è necessario gestire la memoria e non è necessario gestire operazioni di copia di grandi dimensioni.


In che modo questo approccio è diverso da quello che Nick Haddad ha suggerito usando Boost Pointer Container Library?
Thorsten Schöning il

10
@ ThorstenSchöning std :: shared_ptr non aggiunge dipendenza al boost.
James Johnston,

Non puoi usare i puntatori condivisi per gestire il tuo polimorfismo, quindi alla fine perderai questa funzione con questo approccio, a meno che non lanci esplicitamente puntatori
auserdude

11

Generalmente, la memorizzazione degli oggetti direttamente nel contenitore STL è la migliore in quanto è la più semplice, la più efficiente e la più semplice da utilizzare per l'oggetto.

Se l'oggetto stesso ha una sintassi non copiabile o è un tipo di base astratto, sarà necessario archiviare i puntatori (è più semplice usare shared_ptr)


4
Non è molto efficace se i tuoi oggetti sono grandi e muovi gli elementi di frequente.
codice alleato

3

Sembra che tu abbia una buona comprensione della differenza. Se gli oggetti sono piccoli e facili da copiare, allora memorizzali.

Altrimenti, penserei di memorizzare i puntatori intelligenti (non auto_ptr, un riferimento che conta i puntatori intelligenti) in quelli allocati nell'heap. Ovviamente, se si opta per puntatori intelligenti, non è possibile archiviare oggetti allocati nello stack temporaneo (come si è detto).

@ Torbjörn ha un buon punto sull'affettare.


1
Oh, e mai mai mai creare una collezione di auto_ptr di
Torbjörn Gyllebring

Giusto, auto_ptr non è un puntatore intelligente - non fa riferimento al conteggio.
Lou Franco,

auto_ptr non ha neanche una semantica di copia non distruttiva. L'atto di assegnazione di un auto_ptr da onea anotherrilascerà il riferimento da onee cambierà one.
Andy Finkenstadt,


2

Se gli oggetti devono essere indicati altrove nel codice, archiviare in un vettore di boost :: shared_ptr. Ciò garantisce che i puntatori all'oggetto rimarranno validi se si ridimensiona il vettore.

Vale a dire:

std::vector<boost::shared_ptr<protocol> > protocols;
...
connection c(protocols[0].get()); // pointer to protocol stays valid even if resized

Se nessun altro memorizza i puntatori agli oggetti o l'elenco non si ingrandisce e si restringe, basta archiviare come oggetti semplici:

std::vector<protocol> protocols;
connection c(protocols[0]); // value-semantics, takes a copy of the protocol

1

Questa domanda mi ha infastidito per un po '.

Mi affido a memorizzare i puntatori, ma ho alcuni requisiti aggiuntivi (wrapper lua SWIG) che potrebbero non essere applicabili a te.

Il punto più importante in questo post è testarlo tu stesso , usando i tuoi oggetti

L'ho fatto oggi per testare la velocità di chiamata di una funzione membro su una raccolta di 10 milioni di oggetti, 500 volte.

La funzione aggiorna xey in base a xdir e ydir (tutte le variabili dei membri float).

Ho usato un elenco std :: per contenere entrambi i tipi di oggetti e ho scoperto che la memorizzazione dell'oggetto nell'elenco è leggermente più veloce rispetto all'utilizzo di un puntatore. D'altra parte, le prestazioni sono state molto vicine, quindi dipende da come verranno utilizzate nell'applicazione.

Per riferimento, con -O3 sul mio hardware i puntatori hanno impiegato 41 secondi per essere completati e gli oggetti grezzi hanno impiegato 30 secondi per essere completati.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.