Объединения

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

Поля объединения могут быть любых встроенных типов, за исключением строк, динамических массивов и указателей. Также в объединениях можно использовать структуры с такими же простыми типами полей и без конструкторов/деструкторов.

Компилятор выделяет под объединение ячейку памяти размером, равным максимальному размеру среди типов всех элементов. Так, для объединения с полями типа long (8 байтов) и int (4 байта) будет выделено 8 байтов.

Все поля объединения расположены по одному и тому же адресу памяти, то есть они выровнены по началу объединения (имеют смещение 0, что можно проверить с помощью offsetof, см. раздел Упаковка структур).

Синтаксис описания объединения аналогичен структуре, но использует ключевое слово union. За ним идет идентификатор и далее блок кода с перечнем полей.

Например, в алгоритме может использоваться массив типа double для хранения различных настроек, просто потому что тип double один из числа тех, что имеют максимальный размер в байтах: 8. Допустим, среди настроек есть числа типа ulong. Поскольку тип double не гарантирует точное воспроизведение больших значений ulong, требуется воспользоваться объединением для "упаковки" ulong в double и "распаковки" обратно.

#define MAX_LONG_IN_DOUBLE       9007199254740992
// FYI: ULONG_MAX            18446744073709551615
 
union ulong2double
{
   ulong U;   // 8 bytes
   double D;  // 8 bytes
};
ulong2double converter;
 
void OnStart()
{
   Print(sizeof(ulong2double)); // 8
   
   const ulong value = MAX_LONG_IN_DOUBLE + 1;
   
   double d = value// possible loss of data due to type conversion
   ulong result = d// possible loss of data due to type conversion
   
   Print(d" / "value" -> "result);
   // 9007199254740992.0 / 9007199254740993 -> 9007199254740992
   
   converter.U = value;
   double r = converter.D;
   Print(r);               // 4.450147717014403e-308
   Print(offsetof(ulong2doubleU), " "offsetof(ulong2doubleD)); // 0 0
}

Размер структуры ulong2double равен 8, поскольку оба его поля имеют этот размер. Таким образом, поля U и D полностью перекрываются.

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

Стандартная конвертация из ulong в double приводит к потере точности: после записи 9007199254740993 в переменную d типа double мы читаем из неё уже "округленное" значение 9007199254740992 (о тонкостях хранения чисел в типе double см. раздел Вещественные числа).

При использовании конвертера число 9007199254740993 записывается в объединение "как есть", без конвертаций, поскольку мы присваиваем его полю U типа ulong. Его представление с точки зрения double доступно, опять-таки без конвертаций, из поля D. Мы можем его копировать в другие переменные и массивы типа double без опасений.

Хотя полученное значение double выглядит странно, оно в точности соответствует исходному целому, если его потребуется извлечь путем обратной конвертации: записываем в поле D типа double, потом читаем из поля U типа ulong.

Объединение может иметь конструкторы и деструкторы, а также методы. По умолчанию члены объединения имеют публичные права доступа, но это можно откорректировать с помощью модификаторов доступа, как в структуре.