2011-07-08 2 views
26

Sto scrivendo un'applicazione utilizzando openFrameworks, ma la mia domanda non è specifica solo per oF; piuttosto, è una domanda generale sui vettori C++ in generale.Vettore C++ di oggetti rispetto al vettore di puntatori agli oggetti

Volevo creare una classe che contiene più istanze di un'altra classe, ma fornisce anche un'interfaccia intuitiva per l'interazione con tali oggetti. Internamente, la mia classe usava un vettore della classe, ma quando provavo a manipolare un oggetto usando vector.at(), il programma si compilava ma non funzionava correttamente (nel mio caso non avrebbe mostrato un video).

// instantiate object dynamically, do something, then append to vector 
vector<ofVideoPlayer> videos; 
ofVideoPlayer *video = new ofVideoPlayer; 
video->loadMovie(filename); 
videos.push_back(*video); 

// access object in vector and do something; compiles but does not work properly 
// without going into specific openFrameworks details, the problem was that the video would 
// not draw to screen 
videos.at(0)->draw(); 

Da qualche parte, è stato suggerito che faccio un vettore di puntatori a oggetti di quella classe, invece di un vettore di quegli oggetti stessi. Ho implementato questo e in effetti ha funzionato come un fascino.

vector<ofVideoPlayer*> videos; 
ofVideoPlayer * video = new ofVideoPlayer; 
video->loadMovie(filename); 
videos.push_back(video); 
// now dereference pointer to object and call draw 
videos.at(0)->draw(); 

ero l'allocazione di memoria per gli oggetti in modo dinamico, cioè ofVideoPlayer = new ofVideoPlayer;

mia domanda è semplice: perché utilizzando un vettore di lavoro puntatori, e quando si sarebbe creare un vettore di oggetti rispetto a un vettore di puntatori a quegli oggetti?

+0

Possiamo un po 'di codice? Un po 'difficile rispondere a questo da una semplice spiegazione. – MGZero

+0

Non riusciamo a capire perché il tuo codice non ha funzionato se non pubblichi nulla! Per favore aggiungi un esempio che mostri la tua domanda. – Cameron

+0

Il codice importante probabilmente non è l'uso di 'vector', ma la classe' ofVideoPlayer' stessa. –

risposta

23

Quello che devi sapere sui vettori in C++ è che devono usare l'operatore di copia della classe dei tuoi oggetti per poterli inserire nel vettore. Se avessi allocazione di memoria in questi oggetti che sono stati deallocati automaticamente quando è stato chiamato il distruttore, ciò potrebbe spiegare i tuoi problemi: il tuo oggetto è stato copiato nel vettore e poi distrutto.

Se nella classe dell'oggetto è presente un puntatore che punta verso un buffer allocato, una copia di questo oggetto punterà allo stesso buffer (se si utilizza l'operatore di copia predefinito). Se il distruttore rilascia il buffer, quando verrà chiamato il distruttore di copia, il buffer originale verrà deallocato, pertanto i dati non saranno più disponibili.

Questo problema non si verifica se si usano puntatori, perché si controlla la vita dei propri elementi tramite new/destroy e il vettore funziona solo per copiare il puntatore verso i propri elementi.

+1

a) Tuttavia, costruzione potenzialmente pericolosa: 'ofVideoPlayer * video = new ofVideoPlayer;' e 'push_back' questa variabile locale su un vettore. È garantito che funzioni solo quando 'video' non lascia il contesto fino a quando' vector videos' lo usa (ad es. Sono allocati nello stesso contesto dell'esempio). b) Passa per valore (codice che fallisce) è più sicuro - non capisco perché "il tuo oggetto è stato copiato nel' vector' ** e poi distrutto. ** "Distrutto - perché? Si dovrebbe dire, è un problema con il costruttore di copia errato/senza. –

3

vector aggiunta e manutenzione interna utilizzano le copie dell'oggetto originale - se la copia è molto costosa o impossibile, è preferibile utilizzare un puntatore.

Se si crea un puntatore al membro vector, utilizzare smart pointer per semplificare il codice e ridurre al minimo il rischio di perdite.

Forse la tua classe non fa una copia/assegnazione corretta (cioè profonda)? In tal caso, i puntatori funzionerebbero ma non le istanze oggetto come membro del vettore.

+0

Penso che sia una risposta adeguata, ma non quelli che hanno messo in svantaggio. –

6

Se si assegna memoria per gli oggetti utilizzando new, lo si sta allocando nell'heap. In questo caso, dovresti usare i puntatori. Tuttavia, in C++, la convenzione è generalmente quella di creare tutti gli oggetti nello stack e passare le copie di quegli oggetti invece di passare i puntatori agli oggetti nell'heap.

Perché è meglio? È perché il C++ non ha la garbage collection, quindi la memoria per gli oggetti nell'heap non verrà recuperata a meno che non si sia specificatamente l'oggetto delete. Tuttavia, gli oggetti nello stack vengono sempre distrutti quando lasciano l'ambito. Se si creano oggetti nello stack anziché l'heap, si riduce al minimo il rischio di perdite di memoria.

Se si utilizza lo stack anziché l'heap, sarà necessario scrivere buoni costruttori di copia e distruttori. Costruttori o distruttori di copia scritti male possono causare perdite di memoria o doppie libere.

Se gli oggetti sono troppo grandi per essere copiati in modo efficiente, è possibile utilizzare i puntatori. Tuttavia, dovresti usare i puntatori intelligenti di conteggio dei riferimenti (sia C++ 0x auto_ptr o uno dei puntatori della libreria Boost) per evitare perdite di memoria.

+0

'std :: auto_ptr' è standard. Sarà deprecato in C++ 0x in favore di 'std :: unique_ptr'. verranno anche introdotti 'std :: shared_ptr',' std :: weak_ptr', ecc. Le allocazioni dello stack non sono sempre preferibili, ma l'uso di puntatori intelligenti per la gestione dell'heap è eccezionalmente sicuro. È meglio passare 'const e' oggetti in giro per evitare di copiare le chiamate per ottenere prestazioni. – AJG85

12

mia domanda è semplice: perché utilizzando una vettore di lavoro puntatori, e quando vuoi creare un vettore di oggetti contro un vettore di puntatori a quegli oggetti ?

std :: vector è come una matrice raw allocata con nuova e riallocata quando si tenta di inserire più elementi della dimensione corrente.

Quindi, se contiene puntatori A, è come se si stesse manipolando un array di A *. Quando è necessario ridimensionare (si spinge un elemetn mentre è già piena capacità corrente), verrà creato un altro array A * e verrà copiato da quello precedente.

Se contiene oggetti A, è come se si stesse manipolando un array di A, quindi A dovrebbe essere predefinito-constructible se si verificano realizzazioni automatiche. In questo caso, gli oggetti A interi vengono copiati anche in un altro array.

Vedere la differenza? Gli oggetti A in std :: vector possono cambiare indirizzo se si eseguono alcune manipolazioni che richiedono il ridimensionamento dell'array interno. Ecco da dove provengono la maggior parte dei problemi con il contenimento di oggetti in std :: vector.

Un modo di usare std :: vector senza problemi è quello di allocare una matrice abbastanza grande dall'inizio. La parola chiave qui è "capacità". La capacità di std :: vector è la dimensione reale del buffer di memoria in cui inserisce gli oggetti. Quindi, per impostare la capacità, hai due scelte:

1) dimensiona il tuo std :: vector sulla costruzione per costruire tutto l'oggetto dall'inizio, con il numero massimo di oggetti - che chiamerà i costruttori di ogni oggetto.

2) una volta che lo std :: vector è costruito (ma non hanno nulla in esso), utilizzare è riserva di funzionamento(): si attribuisce una grande abbastanza tampone (si fornisce la dimensione massima del vettore). IT imposterà la capacità. Se premi push_back oggetti in questo vettore o ridimensiona() sotto il limite della dimensione che hai fornito nella chiamata a reserve(), non potrà mai riallineare il buffer interno e gli oggetti non cambieranno la posizione in memoria, rendendo i puntatori a quegli oggetti sempre valido (alcune asserzioni per verificare che il cambiamento di capacità non si verifichi mai è una pratica eccellente).

+0

'A' non deve essere __default__ constructible, btw – RiaD

+3

Questa è una buona risposta, in particolare il commento su come i riferimenti di memoria agli oggetti nel vettore potrebbero puntare a un'area non valida quando il vettore viene ridimensionato a causa di respingimenti di oggetti – nurabha

+1

Risposta migliore e più chiara . – TREMOR

2

Generalmente non memorizzo le classi direttamente in std::vector. La ragione è semplice: non sapresti se la classe è derivata o meno.

E.g.:

In intestazioni:

class base 
{ 
public: 
    virtual base * clone() { new base(*this); }; 
    virtual ~base(){}; 
}; 
class derived : public base 
{ 
public: 
    virtual base * clone() { new derived(*this); }; 
}; 
void some_code(void); 
void work_on_some_class(base &_arg); 

In fonte:

void some_code(void) 
{ 
    ... 
    derived instance; 
    work_on_some_class(derived instance); 
    ... 
} 

void work_on_some_class(base &_arg) 
{ 
    vector<base> store; 
    ... 
    store.push_back(*_arg.clone()); 
    // Issue! 
    // get derived * from clone -> the size of the object would greater than size of base 
} 

quindi preferisco usare shared_ptr:

void work_on_some_class(base &_arg) 
{ 
    vector<shared_ptr<base> > store; 
    ... 
    store.push_back(_arg.clone()); 
    // no issue :) 
} 
2

L'idea principale di utilizzare Vector è per memorizzare gli oggetti in uno spazio continua, quando si utilizza il puntatore o il puntatore intelligente che non accadrà