Что конкретно может пойти не так, при использовании reinterpret_cast для преобразования от базы к потомку?

Рассмотрим следующий код:

class A {/*...*/};
class B : public A {/*...*/};

void func(A*const a)
{
    B*const b = reinterpret_cast<B*const>(a);
    b->do_something();
}

В одном крупном проекте было найдено несколько подобных мест. Я знаю, что это приводит к неопределенному поведению.

Меня интересуют конкретно следующие вопросы:

  1. Если указатель a всегда указывает на объект типа B, то будут ли у нас проблемы?
  2. Если указатель a указывает не на объект типа B, то что?

Ответы (2 шт):

Автор решения: Harry
  1. В этом случае проблем не должно быть (особенно если использовать B*b=(B*)a), но как вы это сможете гарантировать?... Чтобы это гарантировать, следует использовать dynamic_cast с проверкой результата.
  2. Будет задница. Мягко говоря. UB.

P.S. от меня лично - мне ужасно не нравится приведение предка к потомку. У меня убежденность, что это говорит о плохом проекте - грубо говоря, когда, чтоб машина поехала, к ней что-то привязывают скотчем :)

P.P.S. Немного о dynamic_cast - хотя ваш вопрос не о нем, так что если это лишнее - извините..

→ Ссылка
Автор решения: user7860670
  1. Почти наверняка будут. Например когда указатель на A получен неявным приведением или static_cast указателя на B, но эти классы не обладают свойством pointer interchangeable, или когда он получен посредством reinterpret_cast, но объекты имеют разные выравнивания.
#include <iostream>

class A { int x; };
class B : public A
{
    public: virtual void do_something() { std::cout << "test"; }
};

void func(A*const p_a)
{
    // При reinterpret_cast адрес в указателе не меняется.
    B*const p_b = reinterpret_cast<B*>(p_a);
    // Адрес у указателе неверный, обращение к vtable наверняка приведет к падению.
    ::std::cout << "B *" << static_cast<void *>(p_b) <<std::endl;
    p_b->do_something();
}

int main()
{
    B b{};
    B * p_b{&b};
    ::std::cout << "B *" << static_cast<void *>(p_b) <<std::endl;
   // Адрес подобъекта A отличается от адреса B, из-за наличия vtable.
    A * p_a{p_b};
    ::std::cout << "A *" << static_cast<void *>(p_a) <<std::endl;
    func(p_a);
    return 0;
}

https://godbolt.org/z/YNh9Zv

  1. Конечно будут: помимо всего вышеперечисленного еще и нарушение strict aliasing.
→ Ссылка