Адрес подкласса равен адресу виртуального базового класса? - PullRequest
8 голосов
/ 13 февраля 2020

Все мы знаем, что при использовании простого одиночного наследования адрес производного класса совпадает с адресом базового класса. Множественное наследование делает это неверным.

Виртуальное наследование также делает это неправдой? Другими словами, правильный ли следующий код:

struct A {};

struct B : virtual A
{
    int i;
};

int main()
{
    A* a = new B; // implicit upcast
    B* b = reinterpret_cast<B*>(a); // fishy?
    b->i = 0;

    return 0;
}

Ответы [ 4 ]

5 голосов
/ 13 февраля 2020

Все мы знаем, что при использовании простого одиночного наследования адрес производного класса совпадает с адресом базового класса.

Мне кажется, утверждение неверно. В приведенном ниже коде у нас есть простое (не виртуальное) одиночное (не множественное) наследование, но адреса разные.

class A
{
public:
   int getX()
   {
      return 0;
   }
};

class B : public A
{
public:
   virtual int getY()
   {
      return 0;
   }
};

int main()
{
   B b;
   B* pB = &b;

   //A* pA = dynamic_cast<A*>(pB);
   A* pA = static_cast<A*>(pB);

   std::cout << "The address of pA is: " << pA << std::endl;
   std::cout << "The address of pB is: " << pB << std::endl;

   return 0;
}

, а для VS2015 вывод:

The address of pA is: 006FF8F0
The address of pB is: 006FF8EC

Виртуальное наследование также не соответствует действительности?

Если вы измените наследование в вышеприведенном коде на виртуальное, результат будет таким же. поэтому даже в случае виртуального наследования адреса базовых и производных объектов могут быть разными.

2 голосов
/ 13 февраля 2020

Результат reinterpret_cast<B*>(a); гарантированно указывает только на включающий B объект a, если подобъект a и включающий объект B являются взаимозаменяемыми по указателю , см. [expr.stati c .cast] / 3 стандарта C ++ 17.

Объект производного класса взаимозаменяем с указателем с базовым классом объект, только если производный объект standard-layout , не имеет прямых нестатических c членов данных и объект базового класса является его первым подобъектом базового класса. [basi c .compound] /4.3

Наличие базового класса virtual дисквалифицирует класс как стандартное расположение . [class] /7.2.

Следовательно, поскольку B имеет виртуальный базовый класс и не статический c элемент данных, b не будет указывать на включающий B объект, но вместо этого значение указателя b останется неизменным по сравнению с a.

Доступ к элементу i, как если бы он указывал на объект B, имеет неопределенное поведение .

Любые другие гарантии будут исходить от вашей спецификации c ABI или другой спецификации.

2 голосов
/ 13 февраля 2020

Множественное наследование не соответствует действительности.

Это не совсем правильно. Рассмотрим следующий пример:

struct A {};
struct B : A {};
struct C : A {};
struct D : B, C {};

При создании экземпляра D создаются экземпляры B и C каждый со своим соответствующим экземпляром A. Однако не было бы проблем, если бы экземпляр D имел тот же адрес, что и его экземпляр B, и соответствующий ему экземпляр A. Хотя это и не обязательно, это именно то, что происходит при компиляции с clang 11 и gcc 10:

D: 0x7fffe08b4758 // address of instance of D
B: 0x7fffe08b4758 and A: 0x7fffe08b4758 // same address for B and A
C: 0x7fffe08b4760 and A: 0x7fffe08b4760 // other address for C and A

Влияет ли виртуальное наследование и на ложь

Давайте рассмотрим измененная версия вышеприведенного примера:

struct A {};
struct B : virtual A {};
struct C : virtual A {};
struct D : B, C {};

Использование спецификатора функции virtual обычно используется, чтобы избежать неоднозначных вызовов функций. Поэтому при использовании наследования virtual экземпляры B и C должны создавать общий экземпляр A. При создании экземпляра D мы получаем следующие адреса:

D: 0x7ffc164eefd0
B: 0x7ffc164eefd0 and A: 0x7ffc164eefd0 // again, address of A and B = address of D
C: 0x7ffc164eefd8 and A: 0x7ffc164eefd0 // A has the same address as before (common instance)

Правильно ли следующий код

Здесь нет причин использовать reinterpret_cast, даже более того, это приводит к неопределенному поведению. Вместо этого используйте static_cast:

A* pA = static_cast<A*>(pB);

В этом примере оба приведения ведут себя по-разному. reinterpret_cast будет интерпретировать pB как указатель на A, но указатель pA может указывать на другой адрес, как в примере выше (C против A). Указатель будет корректно активирован, если вы используете static_cast.

0 голосов
/ 13 февраля 2020

Причина, по которой a и b отличаются в вашем случае, заключается в том, что, поскольку A не имеет никакого виртуального метода, A не поддерживает vtable. С другой стороны, B поддерживает vtable.

Когда вы повышаете до A, компилятор достаточно умен, чтобы пропустить vtable, предназначенный для B. И отсюда разница в адресах. Вы не должны reinterpret_cast возвращаться к B, это не сработает.

Чтобы проверить мою заявку, попробуйте добавить метод virtual, скажем virtual void foo() {} в class A. Теперь A также будет поддерживать vtable. Таким образом, снижение (reinterpret_cast) до B вернет вам оригинал b.

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