Spec-Zone.ru › C++

Объявление объединения

Объединение — это специальный тип класса, который может содержать только одно из своих нестатических членов-данных одновременно.

Синтаксис

Спецификатор класса для объявления объединения похож на объявление класса или структуры:

union attr имя_головы_класса { спецификация_члена }
attr - (с C++11) необязательная последовательность любого количества атрибутов
имя_головы_класса - имя объединения, которое определяется. Необязательно предваряется спецификатором_вложенного_имени (последовательность имен и операторов разрешения области видимости, заканчивающаяся оператором разрешения области видимости). Имя может быть опущено, в этом случае объединение является безымянным
спецификация_члена - список спецификаторов доступа, объявления и определения членов-объектов и членов-функций.

Объединение может иметь члены-функции (включая конструкторы и деструкторы), но не виртуальные функции.

Объединение не может иметь базовые классы и не может использоваться как базовый класс.

Объединение не может иметь нестатических членов-данных ссылочных типов.

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

(до C++11)

Если объединение содержит нестатический член-данных с нетривиальной специальной функцией-членом (копирующим/перемещающим конструктором, оператором копирующей/перемещающей присваивания или деструктором), эта функция по умолчанию удалена в объединении и должна быть явно определена программистом.

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

Максимум один член-вариант может иметь инициализатор члена по умолчанию.

(с C++11)

Так же, как и в объявлении структуры, по умолчанию доступ к членам в объединении — public.

Объяснение

Объединение по крайней мере такое же большое, как необходимо для хранения его наибольшего члена-данных, но обычно не больше. Другие члены-данные предназначены для размещения в одних и тех же байтах как часть этого наибольшего члена. Подробности этого размещения определяются реализацией, за исключением того, что все нестатические члены-данные имеют один и тот же адрес. Чтение из члена объединения, который не был последним записанным, является неопределённым поведением. Многие компиляторы реализуют в качестве нестандартного расширения языка возможность чтения неактивных членов объединения.

#include <cstdint>
#include <iostream>
 
union S
{
    std::int32_t n;     // occupies 4 bytes
    std::uint16_t s[2]; // occupies 4 bytes
    std::uint8_t c;     // occupies 1 byte
};                      // the whole union occupies 4 bytes
 
int main()
{
    S s = {0x12345678}; // initializes the first member, s.n is now the active member
    // at this point, reading from s.s or s.c is undefined behavior
    std::cout << std::hex << "s.n = " << s.n << '\n';
    s.s[0] = 0x0011; // s.s is now the active member
    // at this point, reading from n or c is UB but most compilers define it
    std::cout << "s.c is now " << +s.c << '\n' // 11 or 00, depending on platform
              << "s.n is now " << s.n << '\n'; // 12340011 or 00115678
}

Возможный вывод:

s.n = 12345678
s.c is now 0
s.n is now 115678

Каждый член размещается так, как если бы он был единственным членом класса.

Если члены объединения являются классами с пользовательскими конструкторами и деструкторами, для переключения активного члена, как правило, необходимы явный деструктор и размещённый new:

#include <iostream>
#include <string>
#include <vector>
 
union S
{
    std::string str;
    std::vector<int> vec;
    ~S() {} // needs to know which member is active, only possible in union-like class 
};          // the whole union occupies max(sizeof(string), sizeof(vector<int>))
 
int main()
{
    S s = {"Hello, world"};
    // at this point, reading from s.vec is undefined behavior
    std::cout << "s.str = " << s.str << '\n';
    s.str.~basic_string();
    new (&s.vec) std::vector<int>;
    // now, s.vec is the active member of the union
    s.vec.push_back(10);
    std::cout << s.vec.size() << '\n';
    s.vec.~vector();
}

Вывод:

s.str = Hello, world
1
(с C++11)

Если два члена объединения являются стандартными типами, проверка их общего подпоследовательности на любом компиляторе определена корректно.

Жизненный цикл членов

Жизненный цикл члена объединения начинается, когда член становится активным. Если ранее был активен другой член, его жизненный цикл завершается.

При переключении активного члена объединения выражением присваивания вида E1 = E2, которое использует либо встроенный оператор присваивания, либо тривиальный оператор присваивания, для каждого члена объединения X, присутствующего в подвыражениях доступа к члену и индексации массива в E1, который не является классом с нетривиальными или удалёнными конструкторами по умолчанию, если модификация X привела бы к неопределённому поведению по правилам алиасинга типов, в назначенном хранилище неявно создаётся объект типа X; инициализация не выполняется, и начало его жизненного цикла упорядочено после вычисления значений левого и правого операндов и перед присваиванием.

union A { int x; int y[4]; };
struct B { A a; };
union C { B b; int k; };
 
int f()
{
    C c;               // does not start lifetime of any union member
    c.b.a.y[3] = 4;    // OK: "c.b.a.y[3]", names union members c.b and c.b.a.y;
                       // This creates objects to hold union members c.b and c.b.a.y
    return c.b.a.y[3]; // OK: c.b.a.y refers to newly created object
}
 
struct X { const int a; int b; };
union Y { X x; int k; };
 
void g()
{
    Y y = { { 1, 2 } }; // OK, y.x is active union member
    int n = y.x.a;
    y.k = 4;   // OK: ends lifetime of y.x, y.k is active member of union
    y.x.b = n; // undefined behavior: y.x.b modified outside its lifetime,
               // "y.x.b" names y.x, but X's default constructor is deleted,
               // so union member y.x's lifetime does not implicitly start
}

Тривиальные перемещающие конструктор, оператор перемещающей присваивания, (с C++11)копирующие конструктор и оператор копирующей присваивания типов объединения копируют представления объектов. Если источник и место назначения не являются одним и тем же объектом, эти специальные члены-функции начинают жизненный цикл каждого объекта (за исключением объектов, которые не являются подобъектами места назначения или типа с неявным жизненным циклом) вложенного в место назначения, соответствующего вложенному в источник, перед выполнением копирования. В противном случае они ничего не делают. Два объекта объединения имеют один и тот же соответствующий активный член (если таковой имеется) после построения или присваивания посредством тривиальных специальных функций.

Безымянные объединения

Безымянное объединение — это неопределённое объявление объединения, которое не определяет одновременно никакие переменные (включая объекты типа объединения, ссылки или указатели на объединение).

union { спецификация_члена } ;

Безымянные объединения имеют дополнительные ограничения: они не могут иметь члены-функции, не могут иметь статические члены-данные и все их члены-данные должны быть public. Разрешены только объявления нестатических членов-данных и объявления static_assert(с C++11).

Члены безымянного объединения вводятся в окружающую область видимости (и не должны конфликтовать с другими именами, объявленными там).

int main()
{
    union
    {
        int a;
        const char* p;
    };
    a = 1;
    p = "Jennifer";
}

Безымянные объединения в области видимости пространства имён должны быть объявлены static, за исключением случаев, когда они появляются в безымянном пространстве имён.

Типы классов, подобные объединениям

Класс, подобный объединению, — это либо объединение, либо (не-объединение) класс, у которого есть по крайней мере одно безымянное объединение в качестве члена. Класс, подобный объединению, имеет набор членов-вариантов:

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

Классы, подобные объединениям, могут использоваться для реализации размеченных объединений.

#include <iostream>
 
// S has one non-static data member (tag), three enumerator members (CHAR, INT, DOUBLE), 
// and three variant members (c, i, d)
struct S
{
    enum{CHAR, INT, DOUBLE} tag;
    union
    {
        char c;
        int i;
        double d;
    };
};
 
void print_s(const S& s)
{
    switch(s.tag)
    {
        case S::CHAR: std::cout << s.c << '\n'; break;
        case S::INT: std::cout << s.i << '\n'; break;
        case S::DOUBLE: std::cout << s.d << '\n'; break;
    }
}
 
int main()
{
    S s = {S::CHAR, 'a'};
    print_s(s);
    s.tag = S::INT;
    s.i = 123;
    print_s(s);
}

Вывод:

a
123

Библиотека стандартной библиотеки C++ включает std::variant, которая может заменить многие использования объединений и классов, подобных объединениям. Приведённый выше пример может быть переписан как

#include <iostream>
#include <variant>
 
int main()
{
    std::variant<char, int, double> s = 'a';
    std::visit([](auto x){ std::cout << x << '\n';}, s);
    s = 123;
    std::visit([](auto x){ std::cout << x << '\n';}, s);
}

Вывод:

a
123
(с C++17)

Отчёты об ошибках

Следующие отчёты об ошибках, изменяющие поведение, были применены ретроактивно к ранее опубликованным стандартам C++.

DR Применён к Поведение, как опубликовано Корректное поведение
CWG 1940 C++11 безымянные объединения разрешали только нестатические члены-данные static_assert также разрешены

См. также

variant
(C++17)
тип-безопасное объединение с дискриминатором
(шаблон класса)
C документация для Объявление объединения

© cppreference.com
Licensed under the Creative Commons Attribution-ShareAlike Unported License v3.0.
https://en.cppreference.com/w/cpp/language/union

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API