2016-06-06 46 views
5

Non capisco una cosa. Ad esempio, dichiaro di classe A e di classe B, che è un figlio di A:Oggetti dinamici C++. Come viene determinata la dimensione dell'oggetto durante il runtime?

class A { 
    public: 
     int a; 
} 

class B : public A { 
    public: 
     int b; 
} 

Ovviamente, se creo istanze di A o B, la loro dimensione nella memoria può essere determinato dal tipo.

A instanceA; // size of this will probably be the size of int (property a) 
B instanceB; // size of this will probably be twice the size of int (properties a and b) 

Ma cosa succede se creo istanze dinamiche e poi le liberiamo in seguito?

A * instanceAPointer = new A(); 
A * instanceBPointer = new B(); 

Queste sono istanze di classi diverse, ma il programma li considereranno come istanze della classe A. Questo va bene durante l'utilizzo di loro ma per quanto riguarda liberandoli? Per liberare memoria allocata, il programma deve conoscere la dimensione della memoria da liberare, giusto?

Quindi, se scrivo

delete instanceAPointer; 
delete isntanceBPointer; 

Come funziona il programma sa, la quantità di memoria, a partire dall'indirizzo ogni puntatore punta a, si deve liberare? Perché ovviamente gli oggetti hanno dimensioni diverse, ma il programma li considera di tipo A.

Grazie

+1

Forse questo collegamento sarà d'aiuto? http://www.openrce.org/articles/files/jangrayhood.pdf – OldProgrammer

+1

In realtà, sembra che il secondo potrebbe causare una perdita di memoria, dal momento che non è una classe polimorfica. Se la classe è polimorfica, il compilatore è in grado di deallocarla in base al tipo dinamico, utilizzando RTTI o qualche altro metodo e liberando automaticamente la stessa quantità di memoria effettivamente allocata. Se la classe non è polimorfica, tuttavia, non credo che sia garantita la possibilità di gestire correttamente questa situazione, quindi è sempre necessario eliminarla tramite un puntatore del tipo corretto. –

+0

@JustinTime Per classe polimorfica intendete classe con distruttore virtuale? –

risposta

6

Ho intenzione di supporre che tu sappia come delete works.

Riguardo a come delete sa come pulire un'istanza ereditata. Ecco perché usi un virtual destructor nel contesto dell'ereditarietà, altrimenti avrai un comportamento indefinito. Fondamentalmente, il distruttore, come ogni altra funzione virtual, viene richiamato tramite vtable.

Ricordiamo inoltre che: Il compilatore C++ distrugge implicitamente la classe genitore (i) nel vostro distruttore

class A { 
    public: 
     int a; 
    virtual ~A(){} 
} 

class B : public A { 
    public: 
     int b; 
    ~B() { /* The compiler will call ~A() at the very end of this scope */ } 
} 

Ecco perché questo lavoro;

A* a = new B(); 
delete a; 

Mediante vtable, il distruttore ~B() sarà chiamato da delete. Poiché il compilatore inserisce implicitamente la chiamata del distruttore delle classi di base nelle classi derivate, il distruttore di A verrà chiamato in ~B().

+1

fantastico, grazie! –

+0

quindi, se non dichiaro un distruttore virtuale, il programma potrebbe o meno bloccarsi a seconda dell'implementazione del compilatore? –

3

il comportamento è indefinito se si elimina un oggetto attraverso un puntatore ad un oggetto secondario di base e la classe del sotto-oggetto non non avere un distruttore virtuale.

D'altra parte, se ha un distruttore virtuale, il meccanismo di invio virtuale si occupa di deallocare la quantità corretta di memoria per l'indirizzo corretto (vale a dire quello per l'oggetto completo, più derivato).

È possibile scoprire personalmente l'indirizzo dell'oggetto più derivato applicando dynamic_cast<void*> a un puntatore subobject di base appropriato. (Vedi anche this question.)

+3

Questo è corretto per quanto riguarda il comportamento non definito, ma non corretto per quanto riguarda la deallocazione della memoria. Il numero di byte pari alla dimensione dell'oggetto reale sarà sempre deallocato, anche senza distruttore virtuale. Tuttavia, il distruttore appropriato non verrà chiamato, ovviamente. – SergeyA

+0

@SergeyA: Senza il distruttore virtuale, non si trova nemmeno l'indirizzo * corretto * per deallocare, molto meno la dimensione ... –

+0

Kerrek, perché? Si rilascia l'intero blocco di memoria, la dimensione è nota per la routine di deallocazione (solitamente perché è prefisso al blocco) e si inizia dall'indirizzo indicato nel puntatore. – SergeyA

2

Per liberare memoria allocata, il programma deve conoscere la dimensione della memoria da liberare, giusto?

Se si considera la libreria C malloc e free, vedrete che non c'è bisogno di specificare la quantità di memoria di essere liberata quando si chiama free, anche se in quel caso free è dotato di un void* così ha nessun modo per dedurlo. Invece, le librerie di allocazione tipicamente registrano o possono dedurre abbastanza sulla memoria fornita, in modo tale che il puntatore da solo sia sufficiente per fare la deallocazione.

Questo rimane vero con la routine C++ deallocazione: se una classe di base fornisce la propria static void operator delete(void*, std::size_t)e il distruttore classe base è virtual, allora sarà passato la dimensione di tipo dinamico. Per impostazione predefinita, la deallocazione termina a ::operator delete(void*) a cui non verrà assegnata alcuna dimensione: le routine di allocazione devono conoscere abbastanza da funzionare.

Ci sono una varietà di modi routine di allocazione possono lavorare, tra cui:

  • memorizzare la dimensione di allocazione

  • ripartizione oggetti di dimensioni simili da un pool di stesse dimensioni blocchi, quali che qualsiasi puntatore in quel pool si riferisce implicitamente a quella dimensione del blocco