static_cast
може да изпълнява само тези прехвърляния, при които разположението на паметта между класовете е известно по време на компилиране. dynamic_cast
може да проверява информация по време на изпълнение, което позволява по-прецизна проверка за коректност на предаването, както и да чете информация по време на изпълнение относно оформлението на паметта.
Виртуалното наследяване поставя информация за времето на изпълнение във всеки обект, която указва какво е оформлението на паметта между Base
и Derived
. Един след друг ли е или има допълнителна празнина? Тъй като static_cast
няма достъп до такава информация, компилаторът ще действа консервативно и просто ще даде грешка на компилатора.
По-подробно:
Помислете за сложна структура на наследяване, където - поради множествено наследяване - има множество копия на Base
. Най-типичният сценарий е диамантено наследство:
class Base {...};
class Left : public Base {...};
class Right : public Base {...};
class Bottom : public Left, public Right {...};
В този сценарий Bottom
се състои от Left
и Right
, където всеки има собствено копие на Base
. Структурата на паметта на всички горепосочени класове е известна по време на компилиране и static_cast
може да се използва без проблем.
Нека сега разгледаме подобна структура, но с виртуално наследяване на Base
:
class Base {...};
class Left : public virtual Base {...};
class Right : public virtual Base {...};
class Bottom : public Left, public Right {...};
Използването на виртуалното наследяване гарантира, че когато Bottom
е създадено, то съдържа само едно копие на Base
, което е споделено между частите на обекта Left
и Right
. Оформлението на обект Bottom
може да бъде например:
Base part
Left part
Right part
Bottom part
Сега помислете, че сте задали Bottom
на Right
(това е валидно замяна). Получавате Right
указател към обект, който е на две части: Base
и Right
имат празнина в паметта между тях, съдържаща (сега неуместната) Left
част. Информацията за тази празнина се съхранява по време на изпълнение в скрито поле на Right
(обикновено наричано vbase_offset
). Можете да прочетете подробностите например тук.
Пропускът обаче няма да съществува, ако просто създадете самостоятелен Right
обект.
Така че, ако ви дам само указател към Right
, вие не знаете по време на компилиране дали е самостоятелен обект или част от нещо по-голямо (напр. Bottom
). Трябва да проверите информацията по време на изпълнение, за да прехвърляте правилно от Right
към Base
. Ето защо static_cast
ще се провали, а dynamic_cast
няма.
Забележка относно dynamic_cast:
Докато static_cast
не използва информация за обекта по време на изпълнение, dynamic_cast
го използва и изисква той да съществува! По този начин последното преобразуване може да се използва само в тези класове, които съдържат поне една виртуална функция (напр. виртуален деструктор)
person
CygnusX1
schedule
09.06.2017