Ответ 1
Адрес не виртуальной функции-члена, ну, вы сказали, он не виртуальный, что означает, что он не должен находиться в виртуальной таблице. Зачем? Ну, это не зависит от типа времени выполнения объекта, а только от статического типа, означающего, что компилятор может определить во время компиляции, какую функцию вызывать, чтобы вызов был разрешен, вместо того, чтобы использовать последнее связывание во время выполнения. Сама функция находится в разделе кода где-то, и поэтому во время компиляции адрес функции вставляется непосредственно на сайт вызова.
Хорошо, теперь на забавные вещи. Я немного искал в списке наблюдателей на визуальной студии, и вот что я нашел:
|---------------------------|
| Derive |
|---------------------------|
| vtable ptr for Base1 (+0) |
| Base1::a (+4) |
|---------------------------|
| vtable ptr for Base2 (+8) |
| Base2::b (+12) |
|---------------------------|
| Derive::c (+16) |
|---------------------------|
|---------------------------|
| Base1 vtable |
|---------------------------|
| Derive::destructor (+0) |
| Derive::print (+4) |
|---------------------------|
|---------------------------|
| Base2 vtable |
|---------------------------|
| Derive::destructor (+0) |
| Derive::print (+4) |
|---------------------------|
Итак, у вас есть свой деструктор дважды, по одному на базу. Если я удалю вторую базу Derive (делая ее только наследующей от Base1), мы получим:
|---------------------------|
| Derive |
|---------------------------|
| vtable ptr for Base1 (+0) |
| Base1::a (+4) |
|---------------------------|
| Derive::c (+8) |
|---------------------------|
|---------------------------|
| Base1 vtable |
|---------------------------|
| Derive::destructor (+0) |
| Derive::print (+4) |
|---------------------------|
Вот скриншот окна списка часов и локальных жителей. Если вы посмотрите на значения в списке часов, вы увидите разрыв между началом объекта Derive и адресом a, то есть где первая vtable подходит (таковая для Base1). А во-вторых, вы найдете тот же самый разрыв между a и b, что и вторая vtable (одна для Base2).
EDIT: EUREKA!
Хорошо, поэтому я запустил этот код в gcc, используя QtCreator для Windows с -fdump-class-hierarchy
, и это дало мне:
Vtable for Derive
Derive::_ZTV6Derive: 10u entries
0 (int (*)(...))0
4 (int (*)(...))(& _ZTI6Derive)
8 (int (*)(...))Derive::~Derive
12 (int (*)(...))Derive::~Derive
16 (int (*)(...))Derive::print
20 (int (*)(...))-8
24 (int (*)(...))(& _ZTI6Derive)
28 (int (*)(...))Derive::_ZThn8_N6DeriveD1Ev
32 (int (*)(...))Derive::_ZThn8_N6DeriveD0Ev
36 (int (*)(...))Derive::_ZThn8_N6Derive5printEv
Итак, мы можем ясно видеть, что действительно есть две записи, которые являются деструкторами класса Derive. Это все еще не отвечает, почему все же мы ищем то, что искали. Ну, я нашел это в GCC Itanium ABI
Записи для виртуальных деструкторов являются фактически парами записей. Первый деструктор, называемый полным деструктором объекта, выполняет уничтожение без вызова delete() объекта. Второй деструктор, называемый удаляющим деструктором, вызывает delete() после уничтожения объекта. Оба уничтожают любые виртуальные базы; отдельная не виртуальная функция, называемая деструктором базового объекта, выполняет уничтожение объекта, но не его виртуальные базовые подобъекты, и не вызывает delete().
Итак, обоснование того, почему есть два, похоже, таково: Скажем, у меня есть A, B. B наследуется от A. Когда я вызываю delete на B, удаление виртуального деструктора является вызовом для B, но не удаление один будет вызываться для A, иначе будет двойное удаление.
Я лично ожидал, что gcc будет генерировать только один деструктор (не удаляющий один) и вместо этого вызывать delete. Вероятно, это то, что делает VS, поэтому я нашел только один деструктор в моей таблице vtable, а не два.
Хорошо, я могу спать сейчас:) Надеюсь, это удовлетворит ваше любопытство!