2016-07-10 81 views
6

Ho trovato alcune altre domande che sembrano implicare la mia domanda, ma nessuna ha risposto direttamente (anche the one con quasi lo stesso nome).Perché l'upcasting di un puntatore figlio a una classe base a volte cambia il valore del puntatore?

Ho una classe figlio C++ derivata da due genitori. Creo un oggetto di quella classe, quindi lancio il suo riferimento a ciascuno dei suoi due genitori. I valori cast non sono gli stessi, sebbene uno rimanga il valore del riferimento figlio. Ecco il codice:

class Mom 
{ 
public: 
    int a = 1; 
}; 

class Dad 
{ 
public: 
    int b = 2; 
}; 

class Child : public Mom, public Dad 
{ 
public: 
    int c = 3; 
}; 

int main() 
{ 
    Child* c = new Child; // 0x00C5A618 

    Mom* m = c; // 0x00C5A618 
    Dad* d = c; // 0x00C5A61C 

    return 0; 
} 

Ora, sto indovinando che gli indirizzi ottenuti applicando static_cast <> sono quelle degli oggetti secondari attuali. Come succede, Mom è il primo subobject, quindi in realtà ha lo stesso indirizzo di Child. Dad è il secondo subobject, quindi risiede su un indirizzo diverso.

Da quanto ho capito, in stile C calchi, come questo

Mom* m = (Mom*)c; 
Dad* d = (Dad*)c; 

si applicherà uno static_cast <> quando sarebbe legale per farlo. Infatti, quando effettivamente compilo ed eseguo quella versione, vedo lo stesso comportamento di prima.

Continuando questa saga scintillante, con cast impliciti, come questo

Mom* m = c; 
Dad* d = c; 

lo stesso comportamento pure, da cui deduco che cast impliciti si applicano anche static_cast <> quando il cast esplicito equivalente sarebbe legale.

downcasting torna a Child fa la stessa cosa:

Child* k = static_cast<Child*>(d); // 0x00C5A618 

Il valore in k è diverso dal valore in d, essendo stato arretrato al valore originariamente nel c.

sto indovinando che, in questo caso, non v'è un controllo in fase di esecuzione per vedere se, in effetti, d punti per un Dad sotto-oggetto di un oggetto Child.

Finora, tutto questo ha senso, ma sono sorpreso che non ci sia una tale regolazione degli indirizzi (se è la cosa giusta chiamarla) per le gerarchie derivate singolarmente. Cioè, se la mia gerarchia è semplicemente Mom ->Dad ->Child, lanciando un riferimento a un Child in un puntatore, o un Mom o un Dad non cambia mai il valore:

class Mom 
{ 
public: 
    int a = 1; 
}; 

class Dad : public Mom 
{ 
public: 
    int b = 2; 
}; 

class Child : public Dad 
{ 
public: 
    int c = 3; 
}; 

int main() 
{ 
    Child* c = new Child; // 0x00C5A618 

    Mom* m = c; // 0x00C5A618 
    Dad* d = c; // 0x00C5A618 

    Child* k = static_cast<Child*>(d); // 0x00C5A618 

    return 0; 
} 

mi sarei aspettato che per modificare la dimensione della memoria necessaria per ogni sottoclasse aggiuntiva o quattro byte (la dimensione di un int). Ma loro non lo fanno. Tutti i puntatori nell'esempio sopra riportato, c, m, d e k, hanno lo stesso valore.

Così, ora sto indovinando che gli oggetti sono stati disposti in memoria con l'oggetto Mom presso l'indirizzo più basso, il ulteriore spazio necessario per l'oggetto Dad accadrà dopo, e il ulteriore spazio necessario per il Child venendo scorso. Dal momento che un numero Dad è un Mom, l'indirizzo Dad sarebbe lo stesso dell'indirizzo Mom, no?

Allo stesso modo, un Mom è anch'esso un Mom, quindi il suo indirizzo sarebbe anche lo stesso del proprio indirizzo Mom subobject.

Quindi ...

Penso che sta lavorando in questo modo: in memoria, gli oggetti si susseguono in modo tale che, nel puramente singolarmente derivato gerarchia, upcasting e downcasting sempre produce lo stesso indirizzo, mentre in un moltiplicano gerarchia derivata, alcuni upcast producono un indirizzo diverso, perché non tutti i sottooggetti inizieranno dallo stesso indirizzo dell'oggetto derivato. Come questo:

Possible Memory Layout for a Singly Derived Hierarchy 

+-------+-----+-----+---+ 
| Child : Dad : Mom : a | 
|  |  +-----+---+ 
|  |   : b | 
|  +-----------+---+ 
|     : c | 
+-----------------------+ 


Possible Memory Layout for a Multiply Derived Hierarchy 

+-------+-----+---+ 
| Child : Mom : a | 
|  +-----+---+ 
|  : Dad : b | 
|  +-----+---+ 
|    : c | 
+-----------------+ 

dispiace che ho divagato un po ', ma la mia domanda è questa: è il fatto che un upcast ad alcune classi di base di un oggetto si moltiplicano derivato cambia il valore del riferimento a seguito del Infatti il ​​valore post-cast è l'indirizzo dell'oggetto base in memoria?

+1

Sì ........... – Soren

risposta

3

Sì, hai ragione. L'aggiornamento di un puntatore fornisce l'indirizzo del subobject padre che è memorizzato da qualche parte all'interno della classe figlio. Di solito il subobject genitore è memorizzato all'inizio dell'oggetto figlio, quindi i puntatori al subobject secondario e di base sono gli stessi.

In caso di ereditarietà multipla, è impossibile per tutti i genitori occupare la stessa memoria (ignoriamo l'eventuale ottimizzazione di base vuota), quindi tutti, tranne uno, gli indirizzi di classe base dovranno differire dall'indirizzo figlio.

2

sto indovinando che, in questo caso, non v'è un controllo in fase di esecuzione per vedere se, in effetti, d punti ad un Dad sotto-oggetto di un oggetto Child.

No, se si desidera un controllo di runtime, utilizzare dynamic_cast (ma il tipo di origine deve essere polimorfico). Con static_cast il compilatore sarà solo assume il tipo è corretto, altrimenti si verifica un comportamento non definito.

ritengo che funzioni in questo modo: in memoria, gli oggetti si susseguono in modo tale che, in puramente singolarmente derivato gerarchia, upcasting e downcasting sempre produce lo stesso indirizzo

Questo non è garantita; lo standard lo lascia non specificato. Il compilatore non deve disporre la classe base e gli oggetti secondari membri in modo tale che i sottooggetti di classe base vengano prima, anche se sembra che il compilatore lo faccia in questi casi; ma potrebbero esserci altri casi in cui l'indirizzo potrebbe essere diverso, ad esempio se la classe derivata contiene funzioni virtuali e la classe base no. (In questo caso, la disposizione può essere vptr prima, poi classe base suboggetto, quindi sottoggetti utente.)

è il fatto che un upcast ad alcune classi di base di un oggetto moltiplicano derivato cambia il valore del riferimento un risultato del fatto che il valore post-cast è l'indirizzo dell'oggetto base in memoria?

Sì.

+0

Questa è una cosa che strappa la mente! Come un vecchio programmatore C, l'idea che lanciare un puntatore produrrebbe un valore diverso rispetto al puntatore stesso è difficile da accettare. Ulteriore prova del fatto che non si può semplicemente supporre che il codice simile a C in C++ si comporti come sarebbe in C. –