В чем разница между надлежащим определенным объединением и 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
хранятся в одной и той же ячейке памяти, вы, конечно, можете читать и записывать любые типы, как вам нравится, и при условии, что они имеют одинаковый размер хранилища, ни один бит не "потерян".