Странное поведение reinterpret_cast

0

Я только что нашел ошибку в моем коде использования reinterpret_cast, после того как я изменил его на dynamic_cast, проблема исчезла. Затем я пытаюсь повторно создать поведение, используя следующий код:

  1 #include <iostream>
  2
  3 using namespace std;
  4
  5 typedef enum {
  6     ONE, TWO,
  7 } Num;
  8
  9 class B {
 10     public:
 11         virtual Num f() const = 0;
 12 };
 13
 14 class D: public B {
 15     public:
 16         virtual Num f() const { return TWO; }
 17 };
 18
 19 int main()
 20 {
 21     B *b = new D();
 22     cout << "f()=" << reinterpret_cast<D*>(b)->f() << endl << endl;
 23
 24     return 0;
 25 }

Этот код является упрощенной версией ошибки, которую я только что исправил, в основном я пытаюсь повторно создать ошибку, так что, если я не заменю reinterpret_cast на dynamic_cast, в строке 22 возвращается номер перечислимого числа; после того, как я изменил его на dynamic_cast, возвращается правое перечисление.

НО приведенный код действительно работает хорошо, он выводит "1", который является перечислением TWO.

Возможно, у моего упрощения есть некоторые проблемы, но видите ли вы, что у вышеуказанного кода может быть проблема с использованием reinterpret_case?

Да, я знаю, что использование reinterpret_case не имеет смысла, просто мне нравится знать, что происходит.

Теги:

3 ответа

1
Лучший ответ

Вы видите это, потому что B который содержится в D, не существует по тому же адресу. Это, вероятно, связано с реализацией таблиц диспетчеризации виртуальных методов. Язык не дает такой гарантии, поскольку ни B ни D являются типами POD.

reinterpret_cast - это в основном вы говорите компилятору "взять бит-шаблон этого значения и рассматривать его как какой-то другой тип, не меняя его".

Вы коснулись того, что я ищу, о рассылке, но это не так ясно, вы можете уточнить?

C++ не диктует детали компилятора и/или реализации ABI. Таким образом, это не гарантирует, что reinterpret_cast и dynamic_cast будут делать то же самое. Вы наткнулись на случай (иерархия с одним наследством, как указывал AndreyT), где он работает на вашем компиляторе.

Когда вы объявляете класс как обладающий virtual членами, он больше не является типом POD, где содержимое хранимого объекта точно указано в объявлении класса, поскольку компилятор добавляет скрытый "указатель виртуальной таблицы" к началу хранения. Этот указатель указывает на таблицу для каждого класса, содержащую сведения о виртуальных элементах этого класса, такие как указатели на объекты, указывающие на виртуальные методы для этого класса. Например, вы написали:

class B {
    public:
        virtual Num f() const = 0;
};

но то, что хранится для B, вероятно, что-то вроде:

struct __VirtualsForB {
    Num (*f)(const B* this);
};

struct B {
    const __VirtualsForB* const __vtbl;
};

Затем вы пишете:

class D: public B {
    public:
        // BTW, don't need to say 'virtual' here. Virtual-ness is inherited.
        virtual Num f() const { return TWO; }
};

и для хранения это выглядит так:

struct __VirtualsForD {
    __VirtualsForB __super;
};

// This exists since D is not abstract.
extern const __VirtualsForD __virtuals_for_D;

struct D {
    const __VirtualsForD* const __vtbl;
};

Кроме того, компилятор автоматически генерирует некоторый код и ваш виртуальный метод (который, вероятно, не может быть встроен, даже если вы написали его таким образом, поскольку для работы виртуальной таблицы должен быть указатель):

Num __D__f(const B* __in_this)
{
    const D* this = static_cast<const D*>(__in_this);
    return TWO;
}

const __VirtualsForD __virtuals_for_D = { __D__f } ;

Затем, когда вы пишете:

B *b = new D();

что превращается во что-то вроде:

// Allocate a D.
D* _new_D = (D*)operator new(sizeof(D));
// Construct the D.
_new_D.__vtbl = __virtuals_for_D;
B *b = static_cast<B*>(_new_D);

И из-за некоторых случайных фактов этой реализации:

  1. Первым делом в виртуальной таблице для D является виртуальная таблица для B
  2. Первое, что есть в B или D является указателем на виртуальную таблицу класса объектов.

так бывает, что reinterpret_cast и dynamic_cast делают то же самое (а именно, ничего), а ваш cout << reinterpret_cast<D*>(b)->f() преуспевает. Что, кстати, превращается во что-то вроде:

B* __temp = static_cast<B*>(reinterpret_cast<D*>(b));
Num __temp2 = (*__temp.__vtbl.f)(__temp);
std::ostream::operator<<(cout, __temp2);
// ...

Если какое-либо из этих условий было неверным, как это часто бывает при множественном наследовании или наследовании с виртуальными базовыми классами, то reinterpret_cast потерпит неудачу, как вы ожидали.

Это буквально поведение, определяемое реализацией.

  • 0
    Вы касаетесь чего-то, что я ищу, в таблице диспетчеризации, но это не так ясно, вы можете уточнить?
  • 0
    Привет Майк - как ты получил этот код? Это из какого-то инструмента?
Показать ещё 4 комментария
0

В иерархии с одним наследством, когда самый верхний класс иерархии уже является полиморфным, все действия по иерархии делают одно и то же: ничего. Они просто "концептуально" переинтерпретируют значение указателя как значение другого типа. Только dynamic_cast будет выполнять некоторые дополнительные проверки при использовании для downcasts.

По этой причине reinterpret_cast происходит с "работой" для этой цели. И нет способа заставить его не работать, учитывая ваши определения классов.

0

reinterpret_cast не будет корректно обрабатывать отправку. Если вы уже знаете тип, который хотите использовать, используйте static_cast. Если вы не знаете тип, который хотите использовать, используйте dynamic_cast и убедитесь, что указатель, возвращаемый dynamic_cast, действителен.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню