Пожалуйста, позвольте мне начать с цитирования нескольких строк кода вопроса, чтобы установить sh контекст.
A* a;
return (B*)(a);
Почему стиль C сбой приведения?
При приведении указателей (к объектам) приведение в стиле C имеет ту же функциональность, что и reinterpret_cast
, плюс возможность отбрасывать const
и volatile
. См. Ниже, почему reinterpret_cast
терпит неудачу.
Почему reinterpret_cast
терпит неудачу?
A reinterpret_cast
говорит компилятору обрабатывать выражение как если бы он имел новый тип . Используется один и тот же битовый шаблон, просто интерпретируемый по-разному. Это проблема при работе с составным объектом.
Данный объект имеет тип C
, который является производным от A
и B
. Ни A
, ни B
не имеют объектов нулевого размера, что является ключевым фактором. (Классы могут выглядеть пустыми, но, поскольку они имеют виртуальные функции, каждый объект этих классов содержит указатель на таблицу виртуальных функций.) Вот одна из возможных компоновок, в которой мы предполагаем, что размер указателя равен 8:
----------------------------------
| C : | A : pointer to A's table | <-- Offset 0
| | B : pointer to B's table | <-- Offset 8
----------------------------------
Ваш код начинается с указателя на C
, который в итоге сохраняется как указатель на A
. На изображении выше эти адреса численно совпадают. Все идет нормально. Затем вы берете этот адрес и говорите компилятору, что он является указателем на B
, хотя подобъект B
смещен на 8 байтов. Поэтому, когда вы go вызываете b->F()
, программа ищет адрес F
в таблице виртуальных функций A
! Даже если это приведет к действительному указателю на функцию, вы увидите ошибку сегментации, если сигнатура этой функции не совпадает с сигнатурой B::F
. (Другими словами, ожидайте cra sh.)
На ноте более pedanti c, поскольку A
и B
являются несвязанными типами с использованием указателя, созданного вашим актером. приводит к неопределенному поведению. Вышеприведенное просто объясняет, что обычно происходит в этом случае, но технически стандарт позволил бы получить результат «мой компьютер взорвался».
Почему работает dynamic_cast
?
Короче говоря, dynamic_cast
добавит 8 к указателю в момент нажатия клавиши. То, что вы пытаетесь сделать, известно как «sidecast», и это одна из вещей, для которых dynamic_cast
предназначен. (Это 5b в объяснении cppreference о dynamic_cast
.) dynamic_cast
распознает, что то, на что указывает a
, действительно имеет тип C
(наиболее производный тип) и что C
имеет однозначную базу типа B
. Таким образом, приведение вычисляет разницу между смещениями A
и B
в объектах C
и корректирует указатель. Смещение B
равно 8
, а смещение A
равно 0
, поэтому указатель корректируется на 8-0
, в результате чего действительный указатель равен B
.
. указатель на B
фактически указывает на объект типа B
, вызов виртуальной функции B
работает.
Использование static_cast
до go от C*
до B*
работает аналогично, но если, конечно, у вас нет C*
для работы в этом случае.