Почему у нас есть reinterpret_cast в C ++, когда два цепных static_cast могут делать свою работу? - PullRequest
40 голосов
/ 17 февраля 2011

Скажем, я хочу привести A* к char* и наоборот, у нас есть два варианта (я имею в виду, многие из нас думают, что у нас есть два варианта, , потому что , похоже, оба работают! Отсюда путаница!):

struct A
{
    int age;
    char name[128];
};

A a;
char *buffer = static_cast<char*>(static_cast<void*>(&a)); //choice 1
char *buffer = reinterpret_cast<char*>(&a); //choice 2

Оба отлично работают.

//convert back
A *pA = static_cast<A*>(static_cast<void*>(buffer)); //choice 1
A *pA = reinterpret_cast<A*>(buffer); //choice 2

Даже это прекрасно работает!

Так почему же у нас reinterpret_cast в C ++, когда с двумя цепями static_cast может выполнять свою работу?

Некоторые из вас могут подумать, что эта тема является дубликатом предыдущих тем, таких как перечисленные в нижней части этого поста, но это не так. Эти темы обсуждаются только теоретически, но , но ни один из них не дает даже одного примера, демонстрирующего , почему reintepret_cast действительно необходим, и два static_cast будут , безусловно, не удастся , Я согласен, один static_cast потерпит неудачу. Но как насчет двух?

Если синтаксис двух цепочек static_cast выглядит громоздким, то мы можем написать шаблон функции, чтобы сделать его более удобным для программиста:

template<class To, class From>
To any_cast(From v)
{
    return static_cast<To>(static_cast<void*>(v));
}

И тогда мы можем использовать это, как:

char *buffer = any_cast<char*>(&a); //choice 1
char *buffer = reinterpret_cast<char*>(&a); //choice 2

//convert back
A *pA = any_cast<A*>(buffer); //choice 1
A *pA = reinterpret_cast<A*>(buffer); //choice 2

Также обратите внимание на ситуацию, когда any_cast может быть полезным: Правильное приведение для функций чтения и записи fstream .

Так что мой вопрос в основном таков:

  • Почему у нас reinterpret_cast в C ++?
  • Пожалуйста, покажите мне хотя бы один пример, где с двумя цепями static_cast наверняка не выполнит ту же работу?

Ответы [ 7 ]

36 голосов
/ 17 февраля 2011

Есть вещи, которые reinterpret_cast может сделать, что никакая последовательность static_cast s не может сделать (все из C ++ 03 5.2.10):

  • Указатель может быть явно преобразован в любой целочисленный тип, достаточно большой для его хранения.

  • Значение целочисленного типа или типа перечисления может быть явно преобразовано в указатель.

  • Указатель на функцию может быть явно преобразован в указатель на функцию другого типа.

  • Значение типа "указатель на член X типа T1" может быть явно преобразовано в значение типа "указатель на член Y типа T2", если T1 и T2 - оба типа функций или оба типа объектов.

Также из C ++ 03 9.2 / 17:

  • Указатель на объект POD-struct, соответствующим образом преобразованный с использованием reinterpret_cast, указывает на его начальный элемент (или, если этот элемент является битовым полем, то на модуль, в котором он находится), и наоборот.
15 голосов
/ 17 февраля 2011

Вам нужно reinterpret_cast, чтобы получить указатель с жестко закодированным адресом (например, здесь ):

int* pointer = reinterpret_cast<int*>( 0x1234 );

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

6 голосов
/ 17 февраля 2011

Конкретный пример:

char a[4] = "Hi\n";
char* p = &a;

f(reinterpret_cast<char (&)[4]>(p));  // call f after restoring full type
      // ^-- any_cast<> can't do this...

// e.g. given...
template <typename T, int N>   // <=--- can match this function
void f(T (&)[N]) { std::cout << "array size " << N << '\n'; }
5 голосов
/ 17 февраля 2011

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

static_cast говорит, пожалуйста, преобразуйте данные типа X в Y. reinterpret_cast говорит, пожалуйста, интерпретируйте данные в X как Y.

Вполне может быть, что лежащие в основе операции одинаковы, ичто либо будет работать во многих случаях.Но есть концептуальная разница между тем, чтобы сказать: «Пожалуйста, преобразуйте X в Y» и «Да, я знаю, что эти данные объявлены как X, но, пожалуйста, используйте их так, как будто это действительно Y».

3 голосов
/ 17 февраля 2011

Насколько я могу судить, ваш выбор 1 (два цепных static_cast) ужасно неопределенного поведения.Статическое приведение гарантирует только то, что приведение указателя к void * и затем обратно к исходному указателю работает таким образом, что результирующий указатель из этих преобразований все еще указывает на исходный объект.Все остальные преобразования являются UB.Для указателей на объекты (экземпляры определенных пользователем классов) static_cast может изменить значение указателя.

Для reinterpret_cast - он изменяет только тип указателя, и, насколько я знаю, он никогда не касается значения указателя..

Технически говоря, два варианта не эквивалентны.

РЕДАКТИРОВАТЬ: Для справки static_cast описан в разделе 5.2.9 текущего проекта C ++ 0x(извините, у меня нет стандарта C ++ 03, черновик, который я считаю текущим, n3225.pdf).Он описывает все разрешенные преобразования, и я предполагаю, что ничего конкретно не указано = UBТак что он может взорвать ваш компьютер, если он решит это сделать.

0 голосов
/ 11 октября 2014

Использование литья в стиле C не является безопасным. Он никогда не проверяет, чтобы разные типы можно было смешивать вместе. Приведение типов в C ++ помогает вам убедиться, что приведение типов выполняется в соответствии со связанными объектами (на основе используемого вами приведения). Это более рекомендуемый способ использования приведений, чем использование традиционных приведений в стиле C, которые всегда вредны.

0 голосов
/ 17 февраля 2011

Слушайте, люди, вам на самом деле не нужны reinterpret_cast, static_cast или даже два других приведения стилей C ++ (динамические * и const).

Использование приведения в стиле C одновременно и короче, и позволяет вам делать все, что позволяют делать четыре приведения в стиле C ++.

anyType someVar = (anyOtherType)otherVar;

Так зачем использовать приведения в стиле C ++? Читаемость. Во-вторых: потому что более ограниченное приведение обеспечивает большую безопасность кода.

* ладно, вам может понадобиться динамический

...