Неправильный листинг - это листинг или использование, которое является неопределенным поведением

Если я делаю приведение из базового типа в производный, но базовый тип не является экземпляром производного типа, но только использует результат, если он есть, я получаю неопределенное поведение?

Трудно понять, что я спрашиваю? взгляните на этот пример:

struct Animal { int GetType(){...} };
struct Dog : Animal { bool HasLoudBark(){...}};
struct Cat : Animal { bool HasEvilStare(){...} };

Animal * a = ...;
Dog* d = static_cast<Dog*>(a);

if(a->GetType() == DogType && d->HasLoudBark())
    ....

В этом случае может или не быть a Dog. Мы всегда делаем static_cast of a для Dog * d но мы никогда не используем d если мы не уверены в его Dog.

Предполагая, что a не является Dog, это неопределенное поведение в точке броска? Или это определяется так, как мы фактически не используем d если это действительно не Dog?

Приветствуются ссылки на соответствующие части стандарта.

(Да, я знаю, что могу использовать dynamic_cast и RTTI, и, вероятно, это не отличный код, но меня больше интересует, действительно ли это)

Ответы

Ответ 1

Само литье имеет неопределенное поведение. Цитирование С++ 17 (n4659) [expr.static.cast] 8.2.10/11:

PRvalue типа "указатель на cv1 B ", где B является типом класса, может быть преобразовано в prvalue типа "указатель на cv2 D ", где D - это производный класс (раздел 13) из B, если cv2 является такая же CV-квалификация, как или более высокая cv-квалификация, чем cv1.... Если prvalue типа "указатель на cv1 B " указывает на B который на самом деле является подобъектом объекта типа D, результирующий указатель указывает на охватывающий объект типа D В противном случае поведение не определено.

Ответ 2

Это неопределенное поведение, но если бы вы использовали reinterpret_cast вместо static_cast, вы бы отбросили этого демона.

[expr.reinterpret.cast]/7

Указатель объекта может быть явно преобразован в указатель объекта другого типа. Когда prvalue v типа указателя объекта преобразуется в тип указателя объекта "указатель на cv T ", результатом является static_cast<cv T*>(static_cast<cv void*>(v)).

Как отметил пользователь Angew, для этого требуется определенное внутреннее представление, которое гарантирует, что static_cast<void*>(d) == static_cast<void*>(a) когда a == d ". Эти требования выполняются при работе с простым наследованием и объектами с выравниванием по умолчанию.