Странное поведение «этого» при наследовании

#c #oop #inheritance

#c #ооп #наследование

Вопрос:

У меня возникли проблемы с пониманием значения this в следующем примере:

 struct A {
  int i;

  void bar() {
    cout << this << endl;
  }
};

struct B : public A {
  virtual void foo() = 0;
};

struct C : public B {
  void foo() {
    printf("hello world!n");
  }
};

int main (int argc, const char* argv[]) {
  C* c = new C; 
  cout << c << endl;
  c->bar();
  return 0;
}
  

Оба раза, когда я печатаю указатель на консоль, я получаю разные значения. Я ожидал бы, что это будет то же самое, поскольку они оба раза ссылаются на один и тот же экземпляр ?!

Если я удалю либо виртуальную функцию, либо int i in A , она исчезнет. Почему?

Ответ №1:

Но это относится к разным вещам.

 void bar() {
    cout << this << endl;
  }
  

Здесь this есть тип A* . Он указывает на ту часть объекта, которая есть A .

 out << c << endl;
  

Здесь c a C* и указывает на ту часть объекта, которая есть C .

Если A объект совпадает точно с C объектом, указатели совпадают. Если они этого не делают (например, когда C содержит другие элементы (скрытый указатель на vtable) и, следовательно, часть «A» смещена от начала более крупного объекта), то указатели не обязательно совпадают.

Комментарии:

1.Обычно, даже если C у него есть дополнительные члены, они будут одинаковыми. Однако, если C есть какие-либо virtual функции, обычно их ставят перед любыми членами.

2. звучит разумно, однако я действительно удивлен. есть ли какая-либо возможность получить this класс, который был изначально создан?

3. Для дополнительного удовольствия попробуйте добавить это в main : A* c2 = c; cout << c2 << endl;

4. @lucas clemente: Да, dynamic_cast<C>(this) вернет родительский указатель (если это C или NULL, если это не C).

5. @Лукас Клементе: Правильно. Способ обойти это — иметь виртуальный деструктор в A. Это заставит деструктор подключиться к своему родительскому классу перед началом уничтожения.

Ответ №2:

Это оптимизация компилятора. Структура A не имеет никаких виртуальных методов, поэтому ей не нужна v-таблица. Нет смысла хранить указатель на него в объекте типа A. Что делает макет объекта различным для объектов типа A и C, поскольку C нуждается в v-таблице. Компилятор компенсирует разницу, увеличивая это значение перед вызовом метода A.

Добавьте произвольный виртуальный метод в A, чтобы сопоставить указатели.

Комментарии:

1. Даже с виртуальным методом указатели не совпадают, я только что попробовал.

2. Работает на моей машине ™. В любом случае имеет смысл для меня.