На самом деле, насколько я знаю, безопасного способа восстановления нет.
Если вы посмотрите на адреса памяти для pB и pD, вы заметите, что они не совпадают.
D *pD = new D(); // points at 0x00999720
B *pB = dynamic_cast<B*>(pD); // points at 0x00999730,
// hence inside the memory segment of pD
Так как у вас больше нет первоначального начального адреса, вы не можете восстановить.
Даже reinterpret_cast молча провалится. Это даст вам D *, но с неправильными значениями, так как он будет начинаться с 0x00999730 вместо 0x00999720.
(reinterpret_cast не работает в окне просмотра)
Это приведет к тому же:
(D*)(void*)pB
Работает в окне просмотра, но покажет неправильные значения, поскольку указанная память фактически начинается с 0x00999730 вместо исходного 0x00999720.
В вашем примере reinterpret_cast приведет к:
D* pD2 = reinterpret_cast<D*>(pB); // or "(D*)(void*)pD" in the watch window
pD2
+ B {b=6}
+ A {a=3}
+ C {c=6}
+ A {a=3}
d=6
очевидно, неправильно, должно было быть:
+ B {b=6}
+ A {a=3}
+ C {c=9}
+ A {a=3}
d=12
Так что это оригинальный dynamic_cast, который портится с вещами.
РЕДАКТИРОВАТЬ (дополнительный материал для заметки):
Что запутывает, так это то, что вы предположили, что pB на самом деле все еще был D, а это не так. Из-за виртуального наследования pB фактически указывает на B только тогда, когда он кастуется из D *.
Это связано с тем, как классы представлены внутри.
Нормальное наследование может рассматриваться как приводящее к структуре памяти, подобной этой:
struct A
{
int a;
}
struct B
{
A base
int b;
}
в то время как виртуальное наследование приводит к чему-то вроде этого:
struct A
{
int a;
}
struct B
{
A* base
int b;
}
Это связано с тем, что виртуальное наследование предназначено для предотвращения дублирования, которое осуществляется с помощью указателей. Если у вас есть:
class A
class B: virtual public A
class C: virtual public A
class D: virtual public B, virtual public C
D можно представить примерно так:
struct D
{
B* base1;
C* base2;
int d;
}
, где B и C * A указывают на один и тот же экземпляр A.
Следовательно, когда вы приводите D к B, вместо того, чтобы иметь ту же начальную точку памяти, как в случае обычного одиночного наследования, pB будет указывать на base2 D.
То же самое с не-виртуальным множественным наследованием.
class A
class B
class C: public A, public B
приведет к структуре памяти, которая может быть представлена как:
struct C
{
A base1;
B base2;
int c;
}
Итак, если вы сделаете это:
{
C *pC = new C();
B *pB = dynamic_cast<B*>(pC);
C *pC2 = reinterpret_cast<C*>(pB);
}
это не удастся, так как pB фактически указывает на base2, который не находится на том же адресе памяти, что и pC, который совпадает с base1
ОТКАЗ !!
Приведенное выше представление может быть не совсем правильным. Это упрощенная ментальная модель, которая показала, что работает для меня большую часть времени. Могут быть сценарии, в которых эта модель неверна.
Вывод:
Множественное наследование и любой вид виртуального наследования предотвращают безопасное возвращение reinterpret_cast к подтипу.
Способ, которым MS VC ++ (компилятор C ++, используемый в Visual Studio) реализует не-виртуальное мульти-наследование, которое можно преобразовать из первого базового типа в списке суперклассов обратно в подкласс. Не знаю, соответствует ли это спецификации C ++ или другим компиляторам.