В чем разница между надлежащим определенным объединением и reinterpret_cast?

Можете ли вы предложить хотя бы один сценарий, где есть существенная разница между

union {
T var_1;
U var_2;
}

и

var_2 = reinterpret_cast<U> (var_1)

?

Чем больше я об этом думаю, тем больше они кажутся мне одинаковыми, по крайней мере с практической точки зрения.

Единственное различие, которое я обнаружил, заключается в том, что, хотя размер объединения большой, как самый большой тип данных с точки зрения размера, reinterpret_cast, как описано в этом сообщении, может привести к усечению, поэтому простой старый союз C-стиля еще более безопасен чем новое С++-литье.

Можете ли вы наметить различия между этими 2?

Ответ 1

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

С стандартной точки зрения reinterpret_cast гарантированно работает только для конверсий в оба конца и только если требования к выравниванию типа промежуточного указателя не сильнее, чем для типа источника. Вам не разрешено (*) читать один указатель и читать из другого типа указателя.

В то же время стандарт требует аналогичного поведения из объединений, поведение undefined считывается из члена объединения, отличного от активного (последний элемент, который был записан последним) (+).

Тем не менее, компиляторы часто предоставляют дополнительные гарантии для случая объединения, и все компиляторы, которые я знаю (VS, g++, clang++, xlC_r, intel, Solaris CC), гарантируют, что вы можете читать из союза через неактивный элемент и что он будет выдавать значение с точно такими же битами, что и те, которые были записаны через активный элемент.

Это особенно важно при высокой оптимизации при чтении из сети:

double ntohdouble(const char *buffer) {          // [1]
   union {
      int64_t   i;
      double    f;
   } data;
   memcpy(&data.i, buffer, sizeof(int64_t));
   data.i = ntohll(data.i);
   return data.f;
}
double ntohdouble(const char *buffer) {          // [2]
   int64_t data;
   double  dbl;
   memcpy(&data, buffer, sizeof(int64_t));
   data = ntohll(data);
   dbl = *reinterpret_cast<double*>(&data);
   return dbl;
}

Реализация в [1] санкционирована всеми компиляторами, которых я знаю (gcc, clang, VS, sun, ibm, hp), а реализация в [2] не является и в некоторых из них ужасно потерпит неудачу, когда агрессивная оптимизация используются. В частности, я видел gcc переупорядочивание инструкций и чтение в переменную dbl перед оценкой ntohl, что приводит к неправильным результатам.


(*) За исключением того, что вы всегда можете читать с [signed|unsigned] char* независимо от того, был ли реальный объект (исходный тип указателя).

(+) Снова за некоторыми исключениями, если активный участник имеет общий префикс с другим членом, вы можете прочитать через совместимый член этот префикс.

Ответ 2

Существуют некоторые технические различия между правильным union и a (допустим) правильным и безопасным reinterpret_cast. Однако я не могу придумать ни одного из этих различий, которые невозможно преодолеть.

Настоящая причина предпочесть a union over reinterpret_cast, на мой взгляд, не техническая. Это для документации.

Предположим, что вы создаете кучу классов для представления проводного протокола (который, как я полагаю, является наиболее распространенной причиной использования пиратства в первую очередь), а этот проводной протокол состоит из множества сообщений, подчиненных сообщений и полей. Если некоторые из этих полей являются общими, такие как тип msg, seq # и т.д., Используя объединение, упрощает связывание этих элементов вместе и помогает точно документировать, как протокол появляется на проводе.

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

Ответ 3

В С++ 11 объединение - это тип класса, вы можете удерживать член с нетривиальными функциями-членами. Вы не можете просто переводить из одного члена в другой.

§ 9.5.3

[Пример: рассмотрим следующий союз:

union U {
int i;
float f;
std::string s;
};

Так как std::string (21.3) объявляет нетривиальные версии всех специальных функций-членов, U будет иметь неявно удаленный конструктор по умолчанию, копировать/перемещать конструктор, оператор копирования/перемещения и деструктор. Для использования U некоторые или все эти функции-члены должны быть предоставлены пользователем. - конец примера]

Ответ 4

С практической точки зрения, они, вероятно, на 100% идентичны, по крайней мере, на реальных, неистребимых компьютерах. Вы берете двоичное представление одного типа и записываете его в другой тип.

С точки зрения адвоката языка использование reinterpret_cast четко определено в некоторых случаях (например, указатель на целые преобразования) и специфично для реализации.

Тип соединения в профсоюзе, с другой стороны, очень четко работает undefined, всегда (хотя undefined не обязательно означает, что "не работает" ). В стандарте говорится, что значение не более одного нестатического элемента данных может быть сохранено в союзе в любое время. Это означает, что если вы установите var1, тогда var1 будет действительным, но var2 не будет.
Однако, поскольку var1 и var2 хранятся в одной и той же ячейке памяти, вы, конечно, можете читать и записывать любые типы, как вам нравится, и при условии, что они имеют одинаковый размер хранилища, ни один бит не "потерян".