detalhes deste ponteiro, função virtual e herança múltipla do compilador

Estou lendo o jornal de Bjarne:Herança Múltipla para C ++.

Na seção 3, página 370, Bjarne disse que "o compilador transforma uma chamada de uma função de membro em uma chamada de função" comum "com um argumento" extra "; esse argumento" extra "é um ponteiro para o objeto para o qual a função de membro é chamado."

Estou confuso com o extra deste argumento. Por favor, veja os dois exemplos a seguir:

Exemplo 1: (página 372)

class A {
    int a;
    virtual void f(int);
    virtual void g(int);
    virtual void h(int);
};
class B : A {int b; void g(int); };
class C : B {int c; void h(int); };

Um objeto de classe c C se parece com:

C:

-----------                vtbl:
+0:  vptr -------------->  -----------
+4:  a                     +0: A::f
+8:  b                     +4: B::g
+12: c                     +8: C::h
-----------                -----------  

Uma chamada para uma função virtual é transformada em uma chamada indireta pelo compilador. Por exemplo,

C* pc;
pc->g(2)

torna-se algo como:

(*(pc->vptr[1]))(pc, 2)

O artigo de Bjarne me contou a conclusão acima. A passagemthis O ponto é C *.

No exemplo a seguir, Bjarne contou outra história que me confundiu totalmente!

Exemplo 2: (página 373)

Dadas duas classes

class A {...};
class B {...};
class C: A, B {...};

Um objeto da classe C pode ser apresentado como um objeto contíguo como este:

pc-->          ----------- 
                  A part
B:bf's this--> -----------  
                  B part
               ----------- 
                  C part
               -----------

Chamar uma função de membro de B com um C *:

C* pc;
pc->bf(2); //assume that bf is a member of B and that C has no member named bf.

Bjarne escreveu: "Naturalmente, B :: bf () espera que um B * (se torne esse ponteiro)." O compilador transforma a chamada em:

bf__F1B((B*)((char*)pc+delta(B)), 2);

Por que aqui precisamos de um ponteiro B * para ser othis? Se passarmos um ponteiro * C como othis, ainda podemos acessar os membros de B corretamente, eu acho. Por exemplo, para obter o membro da classe B dentro de B :: bf (), basta fazer algo como: * (this + offset). esse deslocamento pode ser conhecido pelo compilador. Isto está certo?

Perguntas de acompanhamento para os exemplos 1 e 2:

(1) Quando é uma derivação de cadeia linear (exemplo 1), por que o objeto C pode estar no mesmo endereço que o subobjeto B e, por sua vez, A? Não há problema em usar um ponteiro C * para acessar os membros da classe B dentro da função B :: g no exemplo 1? Por exemplo, queremos acessar o membro b, o que acontecerá em tempo de execução? * (pc + 8)?

(2) Por que podemos usar o mesmo layout de memória (derivação de cadeia linear) para a herança múltipla? Supondo que no exemplo 2, classeA, B, C tem exatamente os mesmos membros que o exemplo 1.A: int a ef; B: int b ebf (ou chame-og);C: int c eh. Por que não usar apenas o layout da memória como:

 -----------               
+0:  a                     
+4:  b                    
+8: c                     
-----------   

(3) Eu escrevi um código simples para testar as diferenças entre a derivação da cadeia linear e a herança múltipla.

class A {...};
class B : A {...};
class C: B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;
cout << pc << pb << pa

Mostra quepa, pb epc tem o mesmo endereço.

class A {...};
class B {...};
class C: A, B {...};
C* pc = new C();
B* pb = NULL;
pb = (B*)pc;
A* pa = NULL;
pa = (A*)pc;

Agora,pc epa tem o mesmo endereço, enquantopb é algum deslocamento parapa epc.

Por que a compilação faz essas diferenças?

Exemplo 3: (página 377)

class A {virtual void f();};
class B {virtual void f(); virtual void g();};
class C: A, B {void f();};
A* pa = new C;
B* pb = new C;
C* pc = new C;
pa->f();
pb->f();
pc->f();
pc->g()

(1) A primeira pergunta é sobrepc->g() relacionado à discussão no exemplo 2. A compilação faz a seguinte transformação:

pc->g() ==> g__F1B((*B)((char*)pc+delta(B)))

Ou temos que esperar o tempo de execução fazer isso?

(2) Bjarne escreveu: Ao entrar noC::f, athis ponteiro deve apontar para o início doC objeto (e não para oB parte). No entanto, não é geralmente conhecido em tempo de compilação que oB apontado porpb faz parte de umC então o compilador não pode subtrair a constantedelta(B).

Por que não podemos conhecer oB objeto apontado porpb faz parte de umC no momento da compilação? Com base no meu entendimento,B* pb = new C, pb aponta para um criadoC objeto eC herda deBentão umB ponteiro pb aponta para parte doC.

(3) Suponha que não sabemosB ponteiro para porpb faz parte de umC no momento da compilação. Portanto, temos que armazenar o delta (B) para o tempo de execução que é realmente armazenado com o vtbl. Portanto, a entrada vtbl agora se parece com:

struct vtbl_entry {
    void (*fct)();
    int  delta;
}

Bjarne escreveu:

pb->f() // call of C::f:
register vtbl_entry* vt = &pb->vtbl[index(f)];
(*vt->fct)((B*)((char*)pb+vt->delta)) //vt->delta is a negative number I guess

Estou totalmente confuso aqui. Por que (B *) não um (C *) em(*vt->fct)((B*)((char*)pb+vt->delta))???? Com base no meu entendimento e na introdução de Bjarne na primeira frase da seção 5.1 e na página 377, devemos passar um C * comothis aqui!!!!!!

Seguido pelo trecho de código acima, Bjarne continuou escrevendo: Observe que o ponteiro do objeto pode ter que ser ajustado para po int no subobjeto correto antes de procurar o membro que aponta para o vtbl.

Oh cara!!! Não tenho idéia do que Bjarne tentou dizer? Você pode me ajudar a explicar isso?

questionAnswers(4)

yourAnswerToTheQuestion