Ответ 1
Конечно, значения указателя могут быть разными! Ниже пример, демонстрирующий проблему (вам может потребоваться использовать derived1
в вашей системе вместо derived2
, чтобы получить разницу). Дело в том, что указатель this
обычно корректируется, когда задействовано виртуальное, множественное наследование. Это может быть редкий случай, но это происходит.
Одним из возможных вариантов использования этой идиомы является возможность восстановления объектов известного типа после их хранения как void const*
(или void*
, правильность const
здесь не имеет значения): если у вас есть сложной иерархии наследования, вы не можете просто нарисовать какой-либо нечетный указатель на void*
и надеяться, что сможете восстановить его в исходном типе! То есть, чтобы легко получить, например, указатель на base
(из приведенного ниже примера) и преобразовать его в void*
, вы бы вызвали p->getThis()
, что намного проще static_cast<base*>(p)
и получить void*
, которые можно безопасно отнести к base*
с помощью static_cast<base*>(v)
: вы можете отменить неявное преобразование, но только если вы вернетесь к тому типу, откуда пришел исходный указатель. То есть static_cast<base*>(static_cast<void*>(d))
, где d
является указателем на объект типа, полученного из base
, является незаконным, но static_cast<base*>(d->getThis())
является законным.
Теперь, почему адрес меняется в первую очередь? В примере base
является виртуальным базовым классом из двух производных классов, но может быть и больше. Все подобъекты, класс которых фактически наследуется от base
, будут совместно использовать один общий объект base
в объекте другого производного класса (concrete
в приведенном ниже примере). Местоположение этого субобъекта base
может быть различным относительно соответствующего производного подобъекта в зависимости от того, как упорядочиваются разные классы. В результате указатель на объект base
обычно отличается от указателей на подобъекты классов, фактически наследующих от base
. Соответствующее смещение будет вычисляться во время компиляции, когда это возможно, или из-за чего-то вроде vtable во время выполнения. Смещения корректируются при преобразовании указателей вдоль иерархии наследования.
#include <iostream>
struct base
{
void const* getThis() const { return this; }
};
struct derived1
: virtual base
{
int a;
};
struct derived2
: virtual base
{
int b;
};
struct concrete
: derived1
, derived2
{
};
int main()
{
concrete c;
derived2* d2 = &c;
void const* dptr = d2;
void const* gptr = d2->getThis();
std::cout << "dptr=" << dptr << " gptr=" << gptr << '\n';
}