подробности компилятора об этом указателе, виртуальной функции и множественном наследовании

Я читаю статью Бьярне:Множественное наследование для C ++.

В разделе 3 на странице 370 Бьярне сказал, что «компилятор превращает вызов функции-члена в« обычный »вызов функции с« дополнительным »аргументом; этот« дополнительный »аргумент является указателем на объект, для которого функция-член называется."

Я смущен дополнительным этим аргументом. Пожалуйста, посмотрите следующие два примера:

Пример 1: (стр. 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); };

Объект C класса C выглядит так:

C:

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

Вызов виртуальной функции преобразуется компилятором в косвенный вызов. Например,

C* pc;
pc->g(2)

становится что-то вроде:

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

Бумага Бьярне сообщила мне вышеупомянутое заключение. Прохождениеthis Дело в том, C *.

В следующем примере Бьярн рассказал другую историю, которая полностью смутила меня!

Пример 2: (страница 373)

Дано два класса

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

Объект класса C может быть размечен как непрерывный объект, подобный этому:

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

Вызов функции-члена B с заданным C *:

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

Бьярне писал: «Естественно, B :: bf () ожидает, что B * (станет его указателем)». Компилятор преобразует вызов в:

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

Почему здесь нам нужен указатель B *, чтобы бытьthis? Если мы просто передадим указатель * C в качествеthisЯ думаю, мы все еще можем получить доступ к членам B правильно. Например, чтобы получить член класса B внутри B :: bf (), нам просто нужно сделать что-то вроде: * (this + offset). это смещение может быть известно компилятору. Это правильно?

Последующие вопросы для примера 1 и 2:

(1) Когда это линейный деривационный цепочка (пример 1), почему можно ожидать, что объект C будет иметь тот же адрес, что и B, и, в свою очередь, подобъекты A? Нет проблем с использованием указателя C * для доступа к членам класса B внутри функции B :: g в примере 1? Например, мы хотим получить доступ к члену b, что произойдет во время выполнения? * (Рс + 8)?

(2) Почему мы можем использовать один и тот же макет памяти (линейная цепочка) для множественного наследования? Предполагая в примере 2, классA, B, C имеют точно такие же члены, как в примере 1.A: int a а такжеf; B: int b а такжеbf (или назовите этоg);C: int c а такжеh, Почему бы просто не использовать макет памяти, как:

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

(3) Я написал простой код для проверки различий между производным линейной цепочки и множественным наследованием.

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

Это показывает, чтоpa, pb а такжеpc иметь тот же адрес.

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

Сейчас,pc а такжеpa иметь тот же адрес, в то время какpb какое-то смещение кpa а такжеpc.

Почему компиляция делает эти различия?

Пример 3: (страница 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) Первый вопрос оpc->g() что относится к обсуждению в примере 2. Выполняет ли компиляция следующее преобразование:

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

Или мы должны ждать выполнения для этого?

(2) Бьярне писал: При входе вC::f,this указатель должен указывать на началоC объект (а не кB часть). Однако, как правило, во время компиляции не известно, чтоB указал наpb является частьюC поэтому компилятор не может вычесть константуdelta(B).

Почему мы не можем знатьB объект, на который указываетpb является частьюC во время компиляции? Исходя из моего понимания,B* pb = new C, pb указывает на созданныйC объект иC наследует отBтакB указатель pb указывает на частьC.

(3) Предположим, что мы не знаемB указатель наpb является частьюC во время компиляции. Таким образом, мы должны хранить дельту (B) для среды выполнения, которая фактически сохраняется с помощью vtbl. Итак, запись vtbl теперь выглядит так:

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

Бьярне написал:

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

Я полностью запутался здесь. Почему (B *) не (C *) в(*vt->fct)((B*)((char*)pb+vt->delta))???? Основываясь на моем понимании и введении Бьярне в первом предложении в разделе 5.1 на 377 странице, мы должны передать C * какthis Вот!!!!!!

Вслед за приведенным выше фрагментом кода Бьярне продолжил писать: обратите внимание, что указатель объекта, возможно, придется настроить для указания int на правильный подобъект, прежде чем искать элемент, указывающий на vtbl.

О чувак!!! Я совершенно не представляю, что Бьярн пытался сказать? Можете ли вы помочь мне объяснить это?

Ответы на вопрос(4)

Ваш ответ на вопрос