Тип элемента массива при доступе с полиморфным базовым классом (содержащим виртуальные функции) - PullRequest
1 голос
/ 24 декабря 2011

virtual разрешение функции происходит с указателем / ссылкой, а не с объектом. Теперь рассмотрим пример ниже:

struct Base { virtual void foo (); };
struct Derived : Base { void foo (); };

Derived d[2];
Base *p = d;
p[0].foo();  // calls Derived::foo()!

Мое восприятие было таким: для любого массива T arr[SIZE]; тип arr[N] равен T (а не T&), т.е. arr[N] является объектом. Если бы это было так, то в приведенном выше примере p[0] вызвал бы Base::foo(), потому что p[0] должен разрешиться в объект.

Однако это неправильно. Может кто-нибудь объяснить, почему p[0] разрешает Base&, а не Base? Это потому что p[0].foo() эквивалентно (p+0)->foo()?

Ответы [ 4 ]

2 голосов
/ 24 декабря 2011

Мое восприятие было таким: для любого массива T arr [SIZE];тип arr [N] - это T (а не T &), т.е. arr [N] - это объект.Если бы это было так, то в приведенном выше примере p [0] вызвал бы Base :: foo (), потому что p [0] должен быть разрешен для объекта.

Имейте в виду, что компилятор предположим , что все элементы имеют размер sizeof(T).Это означает, что доступ к любому элементу, кроме первого, будет неопределенным поведением.

1 голос
/ 24 декабря 2011

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

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

1 голос
/ 24 декабря 2011

Однако это неправильно.Может кто-нибудь объяснить, почему p [0] разрешается в Base &, а не Base?Это потому, что p [0] .foo () эквивалентно (p + 0) -> foo ()?

Вы поняли - это «операция косвенного массива».

Следующие выражения эквивалентны:

a[1];
*(a + 1)

Это стандарт: буквально «операция перенаправления массива» означает, что *(a + n) заменяет a[n], поэтому результат является ссылкой наобъект, а не значение объекта, сгенерированное копией.

Это было важно для того, чтобы заставить полиморфизм работать, а также чтобы гарантировать, что производные типы не были "сгенерированы копированием" для базовых типов.далее, следующие также эквивалентны и приводят к ссылке на объект:

a[1][2][3];
*(*(*(a + 1) + 2) + 3);

[EDIT] Чтобы быть явным, в вашем примере, да, они точно эквивалентны:

p[0].foo();
(p+0)->foo();    // The "->" is the "*" indirection
(*(p+0)).foo();
1 голос
/ 24 декабря 2011

Это потому, что p[0].foo() эквивалентно (p+0)->foo()?

Если вы посмотрите на стандарт, это само определение оператора индекса.

Выражение E1[E2] идентично (по определению) *((E1)+(E2))

, и результатом разыменования является «lvalue, относящееся к объекту».Вы теряете информацию о динамическом типе только тогда, когда происходит нарезка (т. Е. Когда запускается конструктор копирования базового типа для создания копии вашего исходного объекта), а здесь это точно не так.

Что касается "фактический сгенерированный код ", я не вижу, в чем проблема ... p[0].foo() означает p->foo();компилятор с радостью сгенерирует код для вызова метода через vptr, который был правильно инициализирован перед конструктором Derived для указания на таблицу Derived vtable.Все это приведет к вызову Derived::foo().

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