Domanda

Ho alcune domande sulla dimensione dell'oggetto con virtual.

1) funzione virtuale

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

La dimensione della Classe A è 8Bytes .... un numero intero (4 byte) più un puntatore virtuale (4 byte) è chiaro!

class B: public A{
    public:
       int b;
       virtual void w();
}

Qual è la dimensione della classe B?Ho testato usando Sizeof B, stampa 12

Significa che esiste un solo vptr, anche se sia la classe B che la classe A hanno una funzione virtuale?Perché c'è un solo vptr?

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

class B {
public:
    int b;
    virtual void w();
};

class C :  public A, public B {
public:
    int c;
    virtual void x();
};

La dimensione di C è 20........

Sembra che in questo caso ci siano due vptr nel layout...Come succede?Penso che i due vptr siano uno per la classe A e un altro per la classe B....quindi non esiste un vptr per la funzione virtuale della classe C?

La mia domanda è: qual è la regola relativa al numero di vptr in eredità?

2) eredità virtuale

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

    class B: virtual public A{                  //virtual inheritance 
    public:
        int b;
        virtual void w();
    };

    class C :  public A {                      //non-virtual inheritance
    public:
        int c;
        virtual void x();
    };

class D: public B, public C {
public:
    int d;
    virtual void y();
};

La dimensione di A è 8 byte -------------- 4(int a) + 4 (vptr) = 8

La dimensione di B è 16 byte -------------- Senza virtuale dovrebbe essere 4 + 4 + 4 = 12.perché ci sono altri 4 byte qui?Qual è il layout della classe B?

La dimensione di C è 12 byte.-------------- 4 + 4 + 4 = 12.È chiaro!

La dimensione di D è 32 byte -------------- dovrebbe essere 16(classe B) + 12(classe C) + 4(int d) = 32.È giusto?

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

    class B: virtual public A{                       //virtual inheritance here
    public:
        int b;
        virtual void w();
    };

    class C :  virtual public A {                    //virtual inheritance here
    public:
        int c;
        virtual void x();
    };

  class D: public B, public C {
   public:
        int d;
        virtual void y();
    };

la dimensione di A è 8

la dimensione di B è 16

la dimensione di C è 16

sizeof D è 28 Significa 28 = 16(classe B) + 16(classe C) - 8(classe A) + 4 (cos'è questo?)

La mia domanda è: perché c'è uno spazio aggiuntivo quando viene applicata l'ereditarietà virtuale?

Qual è la regola sottostante per la dimensione dell'oggetto in questo caso?

Qual è la differenza quando il virtuale viene applicato su tutte le classi base e su parte delle classi base?

È stato utile?

Soluzione

Questo è tutto implementazione definita. Sto utilizzando VC10 Beta2. La chiave per aiutare a capire queste cose (l'implementazione di funzioni virtuali), che c'è da sapere su un interruttore segreto nel compilatore Visual Studio, / d1reportSingleClassLayoutXXX . Io riesco anche a che in un secondo.

La regola di base è il vtable deve essere situato all'offset 0 per ogni puntatore ad un oggetto. Ciò implica multipli VTables per l'ereditarietà multipla.

domande Coppia qui, comincerò in alto:

  

Vuol dire che solo un vptr è lì anche entrambi classe B e classe A hanno funzione virtuale? Perché c'è un solo vptr?

Questo è come funzioni virtuali funzionano, si desidera che la classe base e classe derivata di condividere lo stesso puntatore vtable (indicando l'implementazione della classe derivata.

  

Sembra che in questo caso, due vptrs sono nel layout ..... Come avviene questo? Penso che i due vptrs uno è per la classe A e un altro è per la classe B .... quindi non c'è vptr per la funzione virtuale di classe C?

Questo è il layout di classe C, come riportato da / d1reportSingleClassLayoutC:

class C size(20):
        +---
        | +--- (base class A)
 0      | | {vfptr}
 4      | | a
        | +---
        | +--- (base class B)
 8      | | {vfptr}
12      | | b
        | +---
16      | c
        +---

Lei ha ragione, ci sono due VTables, uno per ogni classe di base. Questo è come funziona in ereditarietà multipla; se C * viene colato in un B *, il valore del puntatore viene regolato 8 byte. Un vtable ha ancora bisogno di essere all'offset 0 per la funzione virtual chiamate a lavorare.

vtable nel layout sopra per la classe A è trattata come vtable classe di C (quando viene chiamato attraverso una C *).

  

Il sizeof B è di 16 byte -------------- Senza virtuale dovrebbe essere 4 + 4 + 4 = 12. Perché c'è un altro 4 byte qui? Qual è il layout della classe B?

Questo è il layout della classe B in questo esempio:

class B size(20):
        +---
 0      | {vfptr}
 4      | {vbptr}
 8      | b
        +---
        +--- (virtual base A)
12      | {vfptr}
16      | a
        +---

Come si può vedere, c'è un puntatore in più per gestire l'ereditarietà virtuale. eredità virtuale è complicato.

  

Il sizeof D è di 32 byte -------------- dovrebbe essere 16 (classe B) + 12 (classe C) + 4 (int d) = 32. È quello giusto?

No, 36 byte. Stessa cosa con l'eredità virtuale. Layout della D in questo esempio:

class D size(36):
        +---
        | +--- (base class B)
 0      | | {vfptr}
 4      | | {vbptr}
 8      | | b
        | +---
        | +--- (base class C)
        | | +--- (base class A)
12      | | | {vfptr}
16      | | | a
        | | +---
20      | | c
        | +---
24      | d
        +---
        +--- (virtual base A)
28      | {vfptr}
32      | a
        +---
  

La mia domanda è, perché c'è uno spazio aggiuntivo quando viene applicata l'ereditarietà virtuale?

Virtual puntatore alla classe base, è complicato. le classi di base sono "uniti" in eredità virtuale. Invece di avere una classe base incorporato in una classe, la classe avrà un puntatore all'oggetto classe base nel layout. Se si dispone di due classi di base che utilizzano l'ereditarietà virtuale (la gerarchia delle classi "diamante"), essi saranno entrambi punto alla stessa classe base virtuale nell'oggetto, invece di avere una copia distinta di quella classe di base.

  

Qual è la regola sottostante per le dimensioni oggetto in questo caso?

punto importante; non ci sono regole: il compilatore può fare quello che deve fare

.

E un ultimo dettaglio; per fare tutti questi diagrammi di layout di classe Sto compilazione con:

cl test.cpp /d1reportSingleClassLayoutXXX

dove XXX è una corrispondenza sottostringa delle struct / classi che si desidera vedere il layout di. Usando questo è possibile esplorare gli effetti di vari schemi di eredità te stesso, così come il motivo / in cui si aggiunge imbottitura, ecc.

Altri suggerimenti

Citazione> La mia domanda è, qual è la regola sul numero di vptrs in eredità?

Non esistono rulez, ogni compilatore fornitore è consentito di implementare la semantica di successione il modo che ritiene più opportuno.

Classe B: public A {}, size = 12. Questo è abbastanza normale, una vtable per B che ha entrambi i metodi virtuali, vtable pointer + 2 * int = 12

Classe C: pubblico A, B pubblica {}, size = 20. C può arbitrariamente estendere vtable di A o B. 2 * puntatore vtable + 3 * int = 20

eredità virtuale: è lì che si ha realmente colpito i bordi del comportamento non documentato. Ad esempio, nel MSVC il vtordisp #pragma e / vd compilazione opzioni diventano rilevanti. C'è un po 'di sfondo informazioni in questo articolo . Ho studiato questo un paio di volte e ha deciso l'acronimo opzione di compilazione era rappresentativo per quello che potrebbe accadere a mio codice se mai usato.

Un buon modo per pensare a questo proposito è quello di capire che cosa deve essere fatto per gestire fino-cast. Cercherò di rispondere alle vostre domande, mostrando il layout di memoria di oggetti di classi che si descrive.

Esempio di codice # 2

Il layout di memoria è la seguente:

vptr | A::a | B::b

upcasting un puntatore a B a tipo A provocherà lo stesso indirizzo, con la stessa vptr utilizzato. Questo è il motivo per cui non c'è alcuna necessità di ulteriore vptr è qui.

Esempio di codice # 3

vptr | A::a | vptr | B::b | C::c

Come si può vedere, ci sono due vptr è qui, proprio come avete indovinato. Perché? Perché è vero che se ci upcast dalla C alla A non abbiamo bisogno di modificare l'indirizzo, e quindi in grado di utilizzare la stessa vptr. Ma se upcast da C a B che fare hanno bisogno che la modifica, e di conseguenza abbiamo bisogno di un vptr all'inizio dell'oggetto risultante.

Così, ogni classe ereditata oltre il primo richiederà un ulteriore vptr (a meno che la classe ereditata non ha metodi virtuali, nel qual caso non ha vptr).

campione

Codice # 4 e oltre

Quando si deriva praticamente, è necessario un nuovo puntatore, chiamato puntatore base , per puntare alla posizione nel layout di memoria delle classi derivate. Non ci può essere più di un puntatore base, naturalmente.

Quindi, come fa il layout della memoria guardare? Questo dipende dal compilatore. Nel vostro compilatore è probabilmente qualcosa come

vptr | base pointer | B::b | vptr | A::a | C::c | vptr | A::a
          \-----------------------------------------^

Ma altri compilatori possono incorporare puntatori di base nella tabella virtuale (utilizzando offset - che merita un'altra questione).

Hai bisogno di una puntatore base, perché quando si deriva in modo virtuale, verrà visualizzata una sola volta nell'arco layout di memoria della classe derivata (può sembrare volte aggiuntivi se è anche derivato normalmente, come nel tuo esempio), in modo che tutti i suoi figli deve essere rivolto verso la stessa identica posizione.

EDIT: chiarimenti - davvero tutto dipende dal compilatore, il layout di memoria ho mostrato può essere diverso in diversi compilatori

.

Tutto questo è completamente implementazione definito ti rendi conto. Non si può contare su qualsiasi di esso. Non v'è alcun 'regola'.

Nell'esempio eredità, ecco come la tabella virtuale per le classi A e B potrebbe essere:

      class A
+-----------------+
| pointer to A::v |
+-----------------+

      class B
+-----------------+
| pointer to A::v |
+-----------------+
| pointer to B::w |
+-----------------+

Come si può vedere, se si dispone di un puntatore alla classe di tavolo virtuale di B, è anche perfettamente valido come tabella virtuale di classe di una.

Nel tuo esempio di classe C, se ci pensate, non c'è modo di fare una tabella virtuale che è sia valida come un tavolo per la classe C, classe A e classe B. Così il compilatore fa due. Una tabella virtuale è valida per la classe A e C (per lo più probabile) e l'altro è valida per la classe A e B.

Questo ovviamente dipende dall'implementazione del compilatore.Ad ogni modo penso di poter riassumere le seguenti regole dall'implementazione data da un classico articolo collegato di seguito e che fornisce il numero di byte che ottieni nei tuoi esempi (tranne la classe D che sarebbe 36 byte e non 32!!!) :

La dimensione di un oggetto di classe T è:

  • La dimensione dei suoi campi PIÙ la somma delle dimensioni di ogni oggetto da cui T eredita PIÙ 4 byte per ogni oggetto da cui T eredita virtualmente PIÙ 4 byte SOLO SE T necessita di UN'ALTRA tabella v
  • Fai attenzione:se una classe K viene virtualmente ereditata più volte (a qualsiasi livello) è necessario aggiungere la dimensione di K una sola volta

Dobbiamo quindi rispondere ad un’altra domanda:Quando una classe ha bisogno di UN'ALTRA tabella v?

  • Una classe che non eredita da altre classi necessita di una v-table solo se dispone di uno o più metodi virtuali
  • ALTRIMENTI, una classe ha bisogno di un'altra v-table SOLO SE NESSUNA delle classi da cui non eredita virtualmente ha una v-table

La fine delle regole (che penso possa essere applicata per corrispondere a ciò che Terry Mahaffey ha spiegato nella sua risposta) :)

Ad ogni modo il mio suggerimento è di leggere il seguente articolo di Bjarne Stroustrup (il creatore del C++) che spiega esattamente queste cose:quante tabelle virtuali sono necessarie con ereditarietà virtuale o non virtuale...e perché!

E' davvero una bella lettura:http://www.hpc.unimelb.edu.au/nec/g1af05e/chap5.html

Non sono sicuro, ma penso che sia a causa del puntatore a metodo virtuale tabella

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top