Объясните механизм преобразования указателя на объект производного класса к указателю на его базовый класс при множественном наследовании: почему числовой адрес указателя может измениться?
При множественном наследовании базовые классы являются отдельными подобъектами одного производного объекта. Поэтому указатель на производный объект при преобразовании к конкретному базовому может получить смещение до начала соответствующего базового подобъекта, и его числовое значение изменится. Это корректное стандартное преобразование, а не изменение самого объекта.
Множественное наследование позволяет одному классу реализовать несколько базовых интерфейсов или включить в объект несколько независимых базовых подобъектов. Чтобы один объект можно было использовать через каждый из этих интерфейсов, C++ представляет указатель на базовый класс как указатель именно на его подобъект.
Такой подход сохраняет корректность доступа к членам базового класса и позволяет виртуальным функциям получать правильный адрес объекта через скрытый параметр this.
Если базовый класс расположен не с начала объекта, адрес производного объекта не совпадает с адресом этого базового подобъекта. Использование исходного адреса без корректировки может привести к чтению чужих данных, вызову функции с неправильным this или неопределённому поведению.
Проблема особенно опасна при замене обычного преобразования на reinterpret_cast: такое преобразование не выполняет требуемую корректировку адреса. Кроме того, если один и тот же базовый класс присутствует в иерархии неоднозначно, неявное преобразование может быть запрещено компилятором.
При преобразовании указателя компилятор знает статические типы производного и базового классов. Он добавляет смещение, соответствующее расположению нужного базового подобъекта внутри производственного объекта. Полученный указатель указывает уже не на весь объект, а на его базовую часть.
Вызов base->right корректен, потому что base указывает на подобъект Right. Обратное преобразование из указателя на базовый класс к производному требует явного указания и должно быть безопасным только тогда, когда базовый указатель действительно обозначает соответствующий подобъект; для проверки во время выполнения применяют dynamic_cast при наличии полиморфного базового класса.
Для виртуального базового класса расположение подобъекта может зависеть от конкретной структуры объекта, поэтому реализация использует дополнительные механизмы доступа к этому подобъекту. Точная схема представления указателей и таблиц не стандартизирована.
Нулевой указатель после корректного преобразования остаётся нулевым. Указатели на разные базовые подобъекты одного объекта могут иметь разные значения, хотя все они относятся к одному объекту верхнего уровня.
Компонент реализует два интерфейса, а конкретный объект наследуется от обоих. Функция получает указатель на второй интерфейс и передаёт его дальше. Рассматривались два варианта: использовать обычное неявное преобразование или преобразовывать адрес через reinterpret_cast.
reinterpret_cast выглядит простым, но не учитывает расположение второго базового подобъекта и может сформировать некорректный указатель. Обычное преобразование безопасно на этапе компиляции, поскольку компилятор знает иерархию, поэтому выбран именно этот вариант. В результате вызовы методов интерфейса получают правильный this, даже если адрес интерфейсного подобъекта отличается от адреса исходного объекта.
Если объект приходит только через базовый полиморфный интерфейс и требуется проверить, относится ли он к нужному производному типу, используется dynamic_cast. Он безопаснее в неизвестной иерархии, но требует RTTI и имеет дополнительную стоимость выполнения.
Меняется ли сам объект при изменении адреса указателя на базовый класс?
Нет. Меняется только представление ссылки на него: указатель начинает обозначать конкретный базовый подобъект внутри того же объекта. Состояние производного объекта и его время жизни от этого преобразования не меняются.
Почему нельзя надёжно восстановить производный указатель простым обратным смещением?
Смещение зависит от конкретной иерархии, а при виртуальном наследовании может определяться структурой объекта и механизмами реализации. Кроме того, базовый указатель может вообще не указывать на подобъект требуемого производного типа. static_cast допустим только при гарантированной корректности отношения объектов, а dynamic_cast проверяет это во время выполнения для подходящей полиморфной иерархии.
Может ли преобразование к базовому классу быть неоднозначным?
Да. Если производный класс содержит два базовых подобъекта одного типа, компилятор не может выбрать, какой из них должен быть адресатом преобразования. Такое преобразование становится ошибкой, пока путь к нужному базовому классу не будет устранён явным указанием или виртуальным наследованием.