2015-06-10 12 views
7

Sto leggendo questo articolo "Virtual method table"virtuale tavolo metodo per multiple-eredità

esempio nell'articolo di cui sopra:

class B1 { 
public: 
    void f0() {} 
    virtual void f1() {} 
    int int_in_b1; 
}; 

class B2 { 
public: 
    virtual void f2() {} 
    int int_in_b2; 
}; 

class D : public B1, public B2 { 
public: 
    void d() {} 
    void f2() {} // override B2::f2() 
    int int_in_d; 
}; 

B2 *b2 = new B2(); 
D *d = new D(); 

Nell'articolo, l'autore introduce che il layout di memoria dell'oggetto d è il seguente:

  d: 
D* d-->  +0: pointer to virtual method table of D (for B1) 
      +4: value of int_in_b1 
B2* b2--> +8: pointer to virtual method table of D (for B2) 
      +12: value of int_in_b2 
      +16: value of int_in_d 

Total size: 20 Bytes. 

virtual method table of D (for B1): 
    +0: B1::f1() // B1::f1() is not overridden 

virtual method table of D (for B2): 
    +0: D::f2() // B2::f2() is overridden by D::f2() 

La domanda è circa d->f2(). La chiamata a d->f2() passa un puntatore B2 come puntatore this quindi dobbiamo fare qualcosa di simile:

(*(*(d[+8]/*pointer to virtual method table of D (for B2)*/)[0]))(d+8) /* Call d->f2() */ 

Perché dovremmo passare un puntatore B2 come non puntatore this il puntatore originale D ??? In realtà stiamo chiamando D :: f2(). Sulla base della mia comprensione, dovremmo passare un puntatore D come this alla funzione D :: f2().

___update____

Se passa un puntatore B2 come this a D :: f2(), Che cosa succede se si vuole accedere ai membri B1 classe D :: f2() ?? Credo che il puntatore B2 (questo) è mostrato in questo modo:

  d: 
D* d-->  +0: pointer to virtual method table of D (for B1) 
      +4: value of int_in_b1 
B2* b2--> +8: pointer to virtual method table of D (for B2) 
      +12: value of int_in_b2 
      +16: value of int_in_d 

Ha già un offset dell'indirizzo iniziale di questo layout memoria contigua certo. Ad esempio, vogliamo accedere a b1 all'interno di D :: f2(), credo in runtime, farà qualcosa di simile: *(this+4) (this punti allo stesso indirizzo di b2) che farebbe riferimento a b2 in B ????

risposta

4

Non è possibile passare il puntatore D a una funzione virtuale che sostituisce B2::f2(), poiché tutte le sostituzioni della stessa funzione virtuale devono accettare il layout di memoria identico.

Da B2::f2() funzione prevede la layout memoria B2 s' dell'oggetto viene passato ad essa come il suo puntatore this, cioè

b2: 
    +0: pointer to virtual method table of B2 
    +4: value of int_in_b2 

funzione prevalente D::f2() deve aspettarsi la stessa disposizione pure. Altrimenti, le funzioni non sarebbero più intercambiabili.

capire perché le questioni di intercambiabilità considerano questo scenario:

class B2 { 
public: 
    void test() { f2(); } 
    virtual void f2() {} 
    int int_in_b2; 
}; 
... 
B2 b2; 
b2.test(); // Scenario 1 
D d; 
d.test(); // Scenario 2 

B2::test() ha bisogno di fare una chiamata di f2() in entrambi gli scenari. Non ha informazioni aggiuntive per dirgli come il puntatore this deve essere regolato quando si effettuano queste chiamate *. Questo è il motivo per cui il compilatore passa il puntatore fisso, quindi la chiamata f2 di test() funzionerebbe sia con D::f2() sia con B2::f2().

* Altre implementazioni possono benissimo passare queste informazioni; tuttavia, l'implementazione dell'eredità multipla discussa nell'articolo non lo fa.

+0

(1) cosa intendi per "intercambiabile", puoi spiegare un po 'più in dettaglio? (2) Se si passa un puntatore 'B2' come' this' a D :: f2(), cosa succede se si vuole accedere ai membri della classe B1 in D :: f2()? Per (2), si prega di consultare l'aggiornamento della domanda. – Fihop

+0

Grazie mille !! Per favore, verificamelo. 'B2 b2; b2.test() ', è un puntatore' B2' passato come 'this' a' B2 :: test() 'sicuramente poiché' b2' è un oggetto standlone. Per 'D d; d.test() ', il compilatore passa il puntatore di correzione che effettivamente punta all'oggetto secondario' B2' di 'D' come' this' a 'test()' dato che la funzione di chiamata effettiva è 'B2 :: test() '. Se 'questo' non punta a' B2' all'interno di 'D', causerebbe problemi quando si accede ai membri di' B2' all'interno della funzione 'B2 :: test()'. Questo è il motivo per cui penso che dovremmo passare un puntatore di correzione come "questo". Questo esempio non può spiegare l'aggiornamento. Grazie ancora – Fihop

+0

Per 'D d; d.test() ', sono d'accordo che facciamo qualcosa come' d.test (B2 * b2) '(significa che' this' punta al sotto-oggetto 'B2' di D). Tuttavia, all'interno di 'B2: test',' b2-> f2() 'dovrebbe eseguire' D :: f2'. Ho ragione? Ora il problema diventa che tipo di 'this' è passato a' b2-> f2() ' – Fihop

1

Data la gerarchia di classi, un oggetto di tipo B2 avrà il seguente footprint di memoria.

+------------------------+ 
| pointer for B2 vtable | 
+------------------------+ 
| int_in_b2    | 
+------------------------+ 

Un oggetto di tipo D avrà la seguente occupazione di memoria.

+------------------------+ 
| pointer for B1 vtable | 
+------------------------+ 
| int_in_b1    | 
+------------------------+ 
| pointer for B2 vtable | 
+------------------------+ 
| int_in_b2    | 
+------------------------+ 
| int_in_d    | 
+------------------------+ 

Quando si utilizza:

D* d = new D(); 
d->f2(); 

Quella chiamata è la stessa:

B2* b = new D(); 
b->f2(); 

f2() può essere chiamato con un puntatore di tipo B2 o puntatore di tipo D. Dato che il runtime deve essere in grado di funzionare correttamente con un puntatore di tipo B2, deve essere in grado di inviare correttamente la chiamata a D::f2() utilizzando il puntatore di funzione appropriato nel vtable di B2. Tuttavia, quando la chiamata viene inviata a D:f2(), il puntatore originale di tipo B2 deve in qualche modo essere adeguatamente compensato in modo che in D::f2(), this punti a D, non a B2.

Ecco il codice di esempio, leggermente modificato per stampare i valori dei puntatori utili e i dati dei membri per aiutare a comprendere le modifiche al valore di this in varie funzioni.

#include <iostream> 

struct B1 
{ 
    void f0() {} 
    virtual void f1() {} 
    int int_in_b1; 
}; 

struct B2 
{ 
    B2() : int_in_b2(20) {} 
    void test_f2() 
    { 
     std::cout << "In B::test_f2(), B*: " << (void*)this << std::endl; 
     this->f2(); 
    } 

    virtual void f2() 
    { 
     std::cout 
     << "In B::f2(), B*: " << (void*)this 
     << ", int_in_b2: " << int_in_b2 << std::endl; 
    } 

    int int_in_b2; 
}; 

struct D : B1, B2 
{ 
    D() : int_in_d(30) {} 
    void d() {} 
    void f2() 
    { 
     // ====================================================== 
     // If "this" is not adjusted properly to point to the D 
     // object, accessing int_in_d will lead to undefined 
     // behavior. 
     // ====================================================== 

     std::cout 
     << "In D::f2(), D*: " << (void*)this 
     << ", int_in_d: " << int_in_d << std::endl; 
    } 
    int int_in_d; 
}; 

int main() 
{ 
    std::cout << "sizeof(void*) : " << sizeof(void*) << std::endl; 
    std::cout << "sizeof(int) : " << sizeof(int) << std::endl; 
    std::cout << "sizeof(B1) : " << sizeof(B1) << std::endl; 
    std::cout << "sizeof(B2) : " << sizeof(B2) << std::endl; 
    std::cout << "sizeof(D)  : " << sizeof(D) << std::endl << std::endl; 

    B2 *b2 = new B2(); 
    D *d = new D(); 
    b2->test_f2(); 
    d->test_f2(); 
    return 0; 
} 

uscita del programma:

sizeof(void*) : 8 
sizeof(int) : 4 
sizeof(B1) : 16 
sizeof(B2) : 16 
sizeof(D)  : 32 

In B::test_f2(), B*: 0x1f50010 
In B::f2(), B*: 0x1f50010, int_in_b2: 20 
In B::test_f2(), B*: 0x1f50040 
In D::f2(), D*: 0x1f50030, int_in_d: 30 

Quando l'oggetto effettivo utilizzato per chiamare test_f2() è D, il valore di this cambia da 0x1f50040 in test_f2() a 0x1f50030 in D::f2(). Corrisponde a sizeof B1, B2 e D. L'offset dell'oggetto di un oggetto D è 16 (0x10). Il valore di this in B::test_f2(), a B*, viene modificato da 0x10 prima che la chiamata venga inviata a D::f2().

Ho intenzione di indovinare che il valore dell'offset da D a B2 è memorizzato nel vtable di B2. In caso contrario, non è possibile che un meccanismo di distribuzione di funzioni generiche possa modificare correttamente il valore di this prima di inviare la chiamata alla funzione virtuale corretta.