Почему это то же самое, даже если указатели объектов отличаются множественным наследованием? - PullRequest
3 голосов
/ 17 ноября 2009

При использовании множественного наследования C ++ должен поддерживать несколько vtables, что приводит к «нескольким представлениям» общих базовых классов.

Вот фрагмент кода:

#include "stdafx.h"
#include <Windows.h>

void dumpPointer( void* pointer )
{
    __int64 thisPointer = reinterpret_cast<__int64>( pointer );
    char buffer[100];
   _i64toa( thisPointer, buffer, 10 );
    OutputDebugStringA( buffer );
    OutputDebugStringA( "\n" );
}

class ICommonBase {
public:
    virtual void Common() = 0 {}
};

class IDerived1 : public ICommonBase {
};

class IDerived2 : public ICommonBase {
};

class CClass : public IDerived1, public IDerived2 {
public:
    virtual void Common() {
        dumpPointer( this );
    }
    int stuff;
};

int _tmain(int argc, _TCHAR* argv[])
{
    CClass* object = new CClass();
    object->Common();
    ICommonBase* casted1 = static_cast<ICommonBase*>( static_cast<IDerived1*>( object ) );
    casted1->Common();
    dumpPointer( casted1 );

    ICommonBase* casted2 = static_cast<ICommonBase*>( static_cast<IDerived2*>( object ) );
    casted2->Common();
    dumpPointer( casted2 );

    return 0;
}

выдает следующий вывод:

206968 //CClass::Common this
206968 //(ICommonBase)IDerived1::Common this
206968 //(ICommonBase)IDerived1* casted1
206968 //(ICommonBase)IDerived2::Common this
206972 //(ICommonBase)IDerived2* casted2

здесь casted1 и casted2 имеют разные значения, что является разумным, поскольку они указывают на разные подобъекты. В тот момент, когда вызывается виртуальная функция, приведение к базовому классу выполнено, и компилятор не знает, что изначально это был самый производный класс. Тем не менее этот одинаков каждый раз. Как это происходит?

Ответы [ 4 ]

3 голосов
/ 17 ноября 2009

Когда множественное наследование используется при вызове виртуальной функции, вызов виртуальной функции часто переходит к «thunk», который регулирует указатель this. В вашем примере запись vtbl указателя casted1 не нуждается в thunk, поскольку подобъект IDerived1 в CClass случайно совпадает с началом объекта CClass (именно поэтому значение указателя casted1 совпадает с указателем CClass object).

Однако указатель casted2 на подобъект IDerived2 не совпадает с началом объекта CClass, поэтому указатель функции vtbl фактически указывает на thunk, а не прямо на CClass::Common() функция. Панель управления настраивает указатель this так, чтобы он указывал на фактический объект CClass, затем переходит к функции CClass::Common(). Таким образом, он всегда будет получать указатель на начало объекта CClass, независимо от того, из какого типа указателя подобъекта он мог быть вызван.

Очень хорошее объяснение этому можно найти в книге S Танли Липпмана «Внутри объектной модели C ++» , раздел 4.2 «Функции виртуальных членов / виртуальные функции в среде MI».

3 голосов
/ 17 ноября 2009

При приведении к другому типу смещения полей, а также записей в vtable должны находиться в согласованном месте. Код, который принимает ICommonBase* в качестве параметра, не знает, что ваш объект действительно IDerived2. Тем не менее, он все еще должен иметь возможность разыменования ->foo или вызова виртуального метода bar(). Если они не по предсказуемым адресам, у которых нет способа работать.

Для случая единственного наследования это легко сделать правильно. Если Derived наследуется от Base, вы можете просто сказать, что смещение 0 в Derived также равно 0 в Base, и элементы, уникальные для Derived, могут идти после последнего члена Base. Очевидно, что для множественного наследования это не может работать, поскольку первый байт Base1 также не может быть первым байтом Base2. Каждому нужно свое место.

Таким образом, если у вас есть такой класс, который наследует от двух (назовите его Foo), компилятор может знать, что для типа Foo часть Base1 начинается со смещения X, а часть Base2 начинается со смещения Y. При приведении к любому из типов компилятор может просто добавить соответствующее смещение к this.

Когда вызывается фактический виртуальный метод Foo, где реализация обеспечивается Foo, ему все еще нужен «реальный» указатель на объект, чтобы он мог получить доступ ко всем его членам, а не только к конкретный экземпляр базы Base1 или Base2. Следовательно, this все еще нужно указывать на "настоящий" объект.

Обратите внимание, что детали реализации этого могут отличаться от описанных, это просто высокоуровневое описание того, почему существует проблема.

1 голос
/ 17 ноября 2009

В G ++ 4.3 есть та же самая объектная модель, которую вы показали (см. Ответ Навина), скажем, что casted1 и casted2 имеют разные значения.

Кроме того, в G ++ 4.3 даже вы используете брутальное приведение:

ICommonBase* casted1 = (ICommonBase*)(IDerived1*)object; 
ICommonBase* casted2 = (ICommonBase*)(IDerived2*)object;

результат тот же.

Очень умный компилятор

1 голос
/ 17 ноября 2009

Если вы видите расположение объекта в памяти для этого, это будет что-то вроде этого:

v-pointer for IDerived1
v-pointer for IDerived2
....
....

Это может быть и в другом случае ... но просто дать идею ..

Ваш this всегда будет указывать на начало объекта, то есть, где хранится v-указатель для IDerived1. Однако, когда вы приведете указатель к IDetived2, приведенный указатель будет указывать на v-указатель для IDerived2, который будет смещен на sizeof (указатель) от this указателя.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...