В чем разница между виртуальным и не виртуальным интерфейсом?

раю» с виртуальным наследованием в C ++ и хочу знать, как устроен объект класса. У меня есть эти три класса:

class A {
private:
    int a;
public:
    A() {this->a = 47;}
    virtual void setInt(int x) {this->a = x;}
    virtual int getInt() {return this->a;}
    ~A() {this->a = 0;}
};

class B {
private:
    int b;
public:
    B() {b = 48;}
    virtual void setInt(int x) {this->b = x;}
    virtual int getInt() {return this->b;}
    ~B() {b = 0;}
};

class C : public A, public B {
private:
    int c;
public:
    C() {c = 49;}
    virtual void setInt(int x) {this->c = x;}
    virtual int getInt() {return this->c;}
    ~C() {c = 0;}
};

(Я думаю, что они правы: p)

я использовал-fdump-class-hierarchy с g ++, и я получил это

Vtable for A
A::_ZTV1A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::_ZTV1A) + 16u)

Vtable for B
B::_ZTV1B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::_ZTV1B) + 16u)

Vtable for C
C::_ZTV1C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& _ZTI1C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& _ZTI1C)
48    C::_ZThn16_N1C6setIntEi
56    C::_ZThn16_N1C6getIntEv

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::_ZTV1C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::_ZTV1C) + 48u)

Теперь, что, черт возьми, эти(int (*)(...))-0x00000000000000010 а такжеC::_ZThn16_N1C6setIntEi and (int (*)(...))0?? Может кто-нибудь объяснить дамп?

Спасибо.

 Martin York05 янв. 2011 г., 23:20
Вы можете использовать C ++ Filter для декодирования_ZTI1C Другие места, вероятно, будут заполнены на более позднем этапе компилятора указателями на функции.
 Martin York05 янв. 2011 г., 23:15
Его не определено. Каждый компилятор (и даже версии компилятора) делают это по-своему.

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

Вот ваш дамп пробежал через C ++ Filter:

Vtable for A
A::vtable for A: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for A)
16    A::setInt
24    A::getInt

Class A
   size=16 align=8
   base size=12 base align=8
A (0x10209fb60) 0
    vptr=((& A::vtable for A) + 16u)

Vtable for B
B::vtable for B: 4u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for B)
16    B::setInt
24    B::getInt

Class B
   size=16 align=8
   base size=12 base align=8
B (0x1020eb230) 0
    vptr=((& B::vtable for B) + 16u)

Vtable for C
C::vtable for C: 8u entries
0     (int (*)(...))0
8     (int (*)(...))(& typeinfo for C)
16    C::setInt
24    C::getInt
32    (int (*)(...))-0x00000000000000010
40    (int (*)(...))(& typeinfo for C)
48    C::non-virtual thunk to C::setInt(int)
56    C::non-virtual thunk to C::getInt()

Class C
   size=32 align=8
   base size=32 base align=8
C (0x1020f5080) 0
    vptr=((& C::vtable for C) + 16u)
  A (0x1020ebd90) 0
      primary-for C (0x1020f5080)
  B (0x1020ebe00) 16
      vptr=((& C::vtable for C) + 48u)

Понятия не имею, что(int (*)(...))-0x00000000000000010 а также(int (*)(...))0 находятся.
C::_ZThn16_N1C6setIntEi/C::non-virtual thunk to C::setInt(int) часть - это «оптимизация вызовов виртуальных функций при наличии множественного или виртуального наследования», как описаноВот.

 David Rodríguez - dribeas06 янв. 2011 г., 00:01
+1 Ударь меня к этому :) Это единственная часть вопроса, которая у меня была ясна - это просто, если ты получаешь и дамп класса, и сборку: сместиthis указатель на 16 и перейти кsetInt/getInt (в каждом случае)
 Eugen Constantin Dinca06 янв. 2011 г., 03:31
@ Давид: если какие-либо другие части станут ясны, пожалуйста, добавьте их в ответ.
 bigxiao15 мая 2018 г., 04:08
В чем разница между виртуальным и не виртуальным интерфейсом?
Решение Вопроса

что этот ответ правильный, но вот мое лучшее предположение.

Если у вас есть класс, который наследует несколько и не виртуально, макет этого класса обычно представляет собой полный объект первого базового типа, затем полный объект второго базового типа, а затем данные для самого объекта. Если вы посмотрите на B, вы увидите указатель vtable для объекта A, а если вы посмотрите на C, вы увидите, что в vtable есть указатели для объектов A и B.

Поскольку объекты расположены таким образом, это означает, что если у вас естьB* указатель, указывающий наC объект, указатель фактически не будет в основе объекта; скорее он будет указывать где-то посередине. Это означает, что если вам когда-либо понадобится привести объект кA*вам нужно будет настроитьB* указатель некоторое количество, чтобы пропустить его обратно к началу объекта. Чтобы сделать это, компилятору нужно где-то кодировать количество байтов, которое нужно пропустить назад, чтобы добраться до начала объекта. Я думаю, что самый первый(int(*)(...)) на самом деле это просто необработанное количество байтов, на которое нужно посмотреть, чтобы добраться до начала объекта. Если вы заметите, дляA vtable этот указатель равен 0 (поскольку vtable для A находится в начале объекта, и то же самое верно дляB vtable (поскольку он также живет в начале объекта. Однако обратите внимание, чтоC vtable состоит из двух частей - первая часть - это vtable дляAи его первая сумасшедшая запись также равна нулю (так как если вы наA vtable, вам не нужно вносить какие-либо корректировки). Однако после первой половины этой таблицыB vtable, и обратите внимание, что его первой записью является шестнадцатеричное значение-0x10, Если вы посмотрите наC расположение объектов, вы заметите, чтоB указатель vtable составляет 16 байт послеA указатель vtable Этот-0x10 значение может быть корректирующим смещением, которое необходимо пропустить черезB указатель vtable для возврата к корню объекта.

Вторая сумасшедшая запись каждого vtable кажется указателем на сам vtable. Обратите внимание, что он всегда равен адресу объекта vtable (сравните имя vtable и то, на что он указывает). Это было бы необходимо, если бы вы хотели выполнить какую-либо идентификацию типа среды выполнения, поскольку это обычно включает просмотр адреса виртуальной таблицы (или, по крайней мере, чего-то рядом с ней)

Наконец, что касается того, почему есть функции с загадочным именем setInt и getInt в концеC Vtable, я уверен, что это потому, чтоC Тип наследует два разных набора функций с именемsetInt а такжеgetInt - через одинA и один черезB, Если бы мне пришлось угадывать, проблема заключается в том, чтобы гарантировать, что внутренние компоненты компилятора могут различать две виртуальные функции.

Надеюсь это поможет!

 David Rodríguez - dribeas05 янв. 2011 г., 23:51
Что касается вторых записей, это, скорее всего, не указатели на vtable, а скорее указатели на объект typeinfo, связанный с этим конкретным экземпляром. Обратите внимание на конкретные значения:A::_ZTV1A против_ZTI1Aи что вптр вA объект установлен, чтобы быть(& A::_ZTV1A - 16u)... они не совпадают.
 templatetypedef05 янв. 2011 г., 23:58
Это оба очень хорошие моменты. Я уверен, что вы правы в обоих случаях.
 David Rodríguez - dribeas05 янв. 2011 г., 23:47
По первому номеру-0x10Я также думал об этом как о смещении подобъекта в конечном объекте. О том, почему это было бы там ... Я не совсем согласен с вашими рассуждениями, так как компилятор просматривает все определения классов, когда он выполняет приведение (неявное или явное), поэтому это не является подсказкой для компилятора. Затем я подумал о других возможных причинах, и единственное, что я могу придумать, это чтобы при удалении через указательBКомпилятор может получить указатель на начало, чтобы освободить память. Но я не уверен в этом.

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