Я слышал, что reinterpret_cast
- это реализация, но я не знаю, что это на самом деле означает. Можете ли вы привести пример того, как это может пойти не так, и все идет не так, лучше ли использовать листинг C-Style?
Reinterpret_cast против C-стиля
Ответ 1
Листинг C-стиля не лучше.
Он просто пытается выполнить различные приведения в стиле С++, пока не найдет тот, который работает. это означает, что когда он действует как a reinterpret_cast
, он имеет те же проблемы, что и reinterpret_cast
. Но, кроме того, он имеет следующие проблемы:
- он может делать много разных вещей, и не всегда ясно, читает ли код, какой тип приведения будет вызываться (он может вести себя как
reinterpret_cast
, aconst_cast
илиstatic_cast
, и это очень разные вещи), - Аналогично, изменение окружающего кода может изменить смысл приведения,
- Трудно найти (
reinterpret_cast
легко найти, что хорошо, потому что отливки уродливы и на них следует обратить внимание при использовании. Но приведение в стиле C, как и в(int)42.0
, намного сложнее найти по поиску)
Чтобы ответить на другую часть вашего вопроса, да, reinterpret_cast
определяется реализацией. Это означает, что когда вы используете его для преобразования из, скажем, int*
в float*
, то у вас нет гарантии, что полученный указатель укажет на тот же адрес. Эта часть определяется реализацией. Но если вы вернете полученные float*
и reinterpret_cast
обратно в int*
, вы получите исходный указатель. Эта часть гарантирована.
Но опять же помните, что это верно, если вы используете reinterpret_cast
или приведение в стиле C:
int i;
int* p0 = &i;
float* p1 = (float*)p0; // implementation-defined result
float* p2 = reinterpret_cast<float*>(p0); // implementation-defined result
int* p3 = (int*)p1; // guaranteed that p3 == p0
int* p4 = (int*)p2; // guaranteed that p4 == p0
int* p5 = reinterpret_cast<int*>(p1); // guaranteed that p5 == p0
int* p6 = reinterpret_cast<int*>(p2); // guaranteed that p6 == p0
Ответ 2
Это реализация, определенная в определенном смысле, что в стандарте (почти) не указано, как должны выглядеть разные значения типов на уровне бит, как должно быть структурировано адресное пространство и т.д. Так что это действительно очень специфичная для конверсий платформа:
double d;
int &i = reinterpret_cast<int&>(d);
Однако, как говорится в стандарте,
Предполагается, что это будет неудивительно для тех, кто знает структуру адресации лежащей в основе машины.
Итак, если вы знаете, что делаете и как все выглядит на низком уровне, ничто не может пойти не так.
Приведение в стиле C несколько похоже на то, что он может выполнять reinterpret_cast, но он также "пытается" static_cast первым, и он может отбрасывать квалификацию cv (в то время как static_cast и reinterpret_cast не могут) и выполнять преобразования без учета контроля доступа (см. 5.4/4 в стандарте С++ 11). Например:.
#include <iostream>
using namespace std;
class A { int x; };
class B { int y; };
class C : A, B { int z; };
int main()
{
C c;
// just type pun the pointer to c, pointer value will remain the same
// only it type is different.
B *b1 = reinterpret_cast<B *>(&c);
// perform the conversion with a semantic of static_cast<B*>(&c), disregarding
// that B is an unaccessible base of C, resulting pointer will point
// to the B sub-object in c.
B *b2 = (B*)(&c);
cout << "reinterpret_cast:\t" << b1 << "\n";
cout << "C-style cast:\t\t" << b2 << "\n";
cout << "no cast:\t\t" << &c << "\n";
}
и вот результат от ideone:
reinterpret_cast: 0xbfd84e78 C-style cast: 0xbfd84e7c no cast: 0xbfd84e78
обратите внимание, что значение, созданное reinterpret_cast, точно такое же, как адрес 'c', в то время как приведение в стиле C привело к правильному указателю смещения.
Ответ 3
Существуют веские причины использовать reinterpret_cast
, и по этим причинам стандарт фактически определяет, что происходит.
Во-первых, используйте непрозрачные типы указателей, либо для API-интерфейсов библиотеки, либо просто для хранения множества указателей в одном массиве (очевидно, вместе с их типом). Вам разрешено преобразовывать указатель в целочисленное число подходящего размера, а затем обратно к указателю, и он будет тем же самым указателем. Например:
T b;
intptr_t a = reinterpret_cast<intptr_t>( &b );
T * c = reinterpret_cast<T*>(a);
В этом коде c
гарантированно указывается на объект b
, как вы ожидали. Конечно, преобразование в другой тип указателя - это undefined (вроде).
Подобные преобразования допускаются для указателей на функции и указателей на функции-члены, но в последнем случае вы можете использовать для/из другого указателя функции-члена просто для переменной, которая является большой enouhg.
Второй случай - использование стандартных типов макета. Это то, что поддерживалось до С++ 11 и теперь указано в стандарте. В этом случае стандарт рассматривает reinterpret_cast как static_cast для void * сначала, а затем static_cast для типа назначения. Это часто используется при выполнении двоичных протоколов, когда структуры данных часто имеют одну и ту же информацию заголовка и позволяют конвертировать типы, имеющие один и тот же макет, но отличающиеся структурой классов С++.
В обоих случаях вы должны использовать явный оператор reinterpret_cast
, а не C-Style. Хотя C-стиль обычно делает то же самое, он может подвергнуться перегруженным операторам преобразования.
Ответ 4
С++ имеет типы, и единственный способ, которым они обычно конвертируются между собой, - это четко определенные операторы преобразования, которые вы пишете. В общем, все, что вам нужно и должно использовать для написания ваших программ.
Иногда, однако, вы хотите переосмыслить биты, представляющие тип, во что-то другое. Обычно это используется для операций с очень низким уровнем, и это не то, что вы обычно должны использовать. Для этих случаев вы можете использовать reinterpret_cast
.
Это реализация определена потому, что стандарт С++ не говорит о том, как вещи должны быть вложены в память. Это контролируется вашей конкретной реализацией С++. Из-за этого поведение reinterpret_cast
зависит от того, как ваш компилятор кладет структуры в памяти и как он реализует reinterpret_cast
.
Приведения в стиле C очень похожи на reinterpret_cast
s, но они имеют гораздо меньший синтаксис и не рекомендуются. Мысль гласит, что кастинг по своей сути является уродливой операцией и требует уродливого синтаксиса, чтобы сообщить программисту, что происходит что-то сомнительное.
Простой пример того, как это может пойти не так:
std::string a;
double* b;
b = reinterpret_cast<double*>(&a);
*b = 3.4;
Это поведение программы undefined - компилятор может делать все, что ему нравится. Скорее всего, вы столкнулись бы с сбоем при вызове деструктора string
, но кто знает! Это может привести к повреждению вашего стека и сбою в несвязанной функции.
Ответ 5
Оба метода reinterpret_cast
и c-style - это реализация, и они делают почти одно и то же. Различия:
1. reinterpret_cast
не может удалить константу. Например:
const unsigned int d = 5;
int *g=reinterpret_cast< int* >( &d );
выдаст ошибку:
error: reinterpret_cast from type 'const unsigned int*' to type 'int*' casts away qualifiers
2. Если вы используете reinterpret_cast
, легко найти места, где вы это сделали. Это невозможно сделать с помощью c-style cast