Когда кто-нибудь будет использовать профсоюз?Это остаток от C-only дней? - PullRequest
122 голосов
/ 25 января 2011

Я научился, но на самом деле не получаю союзов. Каждый текст на C или C ++, который я прохожу, представляет их (иногда мимоходом), но они, как правило, дают очень мало практических примеров того, почему или где их использовать. Когда профсоюзы будут полезны в современном (или даже устаревшем) случае? Мое единственное предположение - программирование микропроцессоров, когда у вас очень ограниченное пространство для работы или когда вы разрабатываете API (или что-то подобное) и хотите заставить конечного пользователя иметь только один экземпляр нескольких объектов / типов в один раз. Эти две догадки даже близко к праву?

Ответы [ 18 ]

96 голосов
/ 25 января 2011

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

struct my_variant_t {
    int type;
    union {
        char char_value;
        short short_value;
        int int_value;
        long long_value;
        float float_value;
        double double_value;
        void* ptr_value;
    };
};

Тогда вы бы использовали его, например:

/* construct a new float variant instance */
void init_float(struct my_variant_t* v, float initial_value) {
    v->type = VAR_FLOAT;
    v->float_value = initial_value;
}

/* Increments the value of the variant by the given int */
void inc_variant_by_int(struct my_variant_t* v, int n) {
    switch (v->type) {
    case VAR_FLOAT:
        v->float_value += n;
        break;

    case VAR_INT:
        v->int_value += n;
        break;
    ...
    }
}

На самом деле это довольно распространенная идиома, особенно для внутренних компонентов Visual Basic.

Реальный пример см. В SDL SDL_Event union . ( фактический исходный код здесь ). В верхней части объединения есть поле type, и то же поле повторяется в каждой структуре события SDL_ *. Затем для обработки правильного события вам необходимо проверить значение поля type.

Преимущества просты: существует один тип данных для обработки всех типов событий без использования ненужной памяти.

81 голосов
/ 25 октября 2013

Я считаю, что союзы C ++ довольно крутые. Кажется, что люди обычно думают только о случае использования, когда кто-то хочет изменить значение экземпляра объединения «на месте» (которое, похоже, служит только для экономии памяти или выполнения сомнительных преобразований).

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

Вариант использования 1: хамелеон

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

struct Batman;
struct BaseballBat;

union Bat
{
    Batman brucewayne;
    BaseballBat club;
};

ReturnType1 f(void)
{
    BaseballBat bb = {/* */};
    Bat b;
    b.club = bb;
    // do something with b.club
}

ReturnType2 g(Bat& b)
{
    // do something with b, but how do we know what's inside?
}

Bat returnsBat(void);
ReturnType3 h(void)
{
    Bat b = returnsBat();
    // do something with b, but how do we know what's inside?
}

Похоже, что программист должен быть уверен в типе содержимого данного экземпляра объединения, когда он хочет его использовать. Дело обстоит так в функции f выше. Однако, если функция получит экземпляр объединения в качестве переданного аргумента, как в случае с g выше, то она не будет знать, что с ним делать. То же самое относится и к функциям, возвращающим экземпляр объединения, см. h: как вызывающая сторона узнает, что находится внутри?

Если экземпляр объединения никогда не передается в качестве аргумента или в качестве возвращаемого значения, то он должен иметь очень монотонную жизнь с всплесками возбуждения, когда программист решает изменить свое содержимое:

Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;

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

Вариант использования 2: «Приятно познакомиться, я object, от Class»

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

Затем программист должен написать switch оператор потока управления, чтобы отличить Брюса Уэйна от деревянной палочки или чего-то подобного. Это не так уж плохо, когда в объединении есть только два типа содержимого, но очевидно, что объединение больше не масштабируется.

Вариант использования 3:

Как утверждают авторы , рекомендация по стандарту ISO C ++ еще в 2008 году

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

А теперь пример с диаграммой классов UML:

many compositions for class A

Ситуация на простом английском языке: объект класса A может иметь объекты любого класса из B1, ..., Bn и не более одного из каждого типа с n будучи довольно большим числом, скажем, по крайней мере 10.

Мы не хотим добавлять поля (элементы данных) в A следующим образом:

private:
    B1 b1;
    .
    .
    .
    Bn bn;

потому что n может отличаться (мы могли бы захотеть добавить классы Bx к смеси), и потому что это могло бы вызвать беспорядок с конструкторами и потому что объекты A занимали бы много места.

Мы могли бы использовать дурацкий контейнер из void* указателей на Bx объектов с приведениями для их извлечения, но это неправильно и так в стиле C ... но что более важно, это оставило бы нам время жизни многих динамически распределенныхобъекты для управления.

Вместо этого можно сделать следующее:

union Bee
{
    B1 b1;
    .
    .
    .
    Bn bn;
};

enum BeesTypes { TYPE_B1, ..., TYPE_BN };

class A
{
private:
    std::unordered_map<int, Bee> data; // C++11, otherwise use std::map

public:
    Bee get(int); // the implementation is obvious: get from the unordered map
};

Затем, чтобы получить содержимое экземпляра объединения из data, вы используете a.get(TYPE_B2).b2 илайки, где a - это экземпляр класса A.

Это тем более мощно, что профсоюзы не ограничены в C ++ 11.Подробнее см. документ, на который есть ссылка или в этой статье .

36 голосов
/ 25 января 2011

Один из примеров - во встроенной области, где каждый бит регистра может означать что-то другое.Например, объединение 8-разрядного целого числа и структуры с 8 отдельными 1-разрядными битовыми полями позволяет изменить один бит или весь байт.

22 голосов
/ 28 января 2011

Херб Саттер написал в GOTW около шести лет назад, с акцентом добавлено:

«Но не думайте, что союзы являются лишь пережитком прежних времен. Союзы, возможно, наиболее полезны для экономии места, позволяя перекрывать данные, , и это все еще желательно в C ++ ив современном современном мире. Например, некоторые из самых современных в мире реализаций стандартных библиотек C ++ в настоящее время используют именно эту технику для реализации «оптимизации небольших строк», отличной альтернативы оптимизациикоторый использует хранилище внутри самого строкового объекта: для больших строк пространство внутри строкового объекта хранит обычный указатель на динамически распределяемый буфер и служебную информацию, такую ​​как размер буфера, для маленьких строк то же пространство используется для хранениясодержимое строки напрямую и полностью исключает динамическое выделение памяти.imization (и другие строковые оптимизации и пессимизации), см. .... "

А для менее полезного примера посмотрите длинный, но неубедительный вопрос gcc, строго псевдонимы,и литье через союз .

19 голосов
/ 25 января 2011

Хорошо, один пример использования, о котором я могу подумать, это:

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} some32bittype;

Затем вы можете получить доступ к 8-битным отдельным частям этого 32-битного блока данных; Тем не менее, подготовьтесь к тому, чтобы быть укушенным порядком байтов.

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

Тем не менее, существует также метод, который является безопасным для байтов:

uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;

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

14 голосов
/ 25 января 2011

Объединения полезны при работе с данными уровня байтов (низкого уровня).

Одним из моих недавних использований было моделирование IP-адресов, которое выглядит следующим образом:

// Composite structure for IP address storage
union
{
    // IPv4 @ 32-bit identifier
    // Padded 12-bytes for IPv6 compatibility
    union
    {
        struct
        {
            unsigned char _reserved[12];
            unsigned char _IpBytes[4];
        } _Raw;

        struct
        {
            unsigned char _reserved[12];
            unsigned char _o1;
            unsigned char _o2;
            unsigned char _o3;
            unsigned char _o4;    
        } _Octet;    
    } _IPv4;

    // IPv6 @ 128-bit identifier
    // Next generation internet addressing
    union
    {
        struct
        {
            unsigned char _IpBytes[16];
        } _Raw;

        struct
        {
            unsigned short _w1;
            unsigned short _w2;
            unsigned short _w3;
            unsigned short _w4;
            unsigned short _w5;
            unsigned short _w6;
            unsigned short _w7;
            unsigned short _w8;   
        } _Word;
    } _IPv6;
} _IP;
11 голосов
/ 25 января 2011

Некоторые варианты использования для союзов:

  • Предоставление общего интерфейса с порядком байтов для неизвестного внешнего хоста.
  • Манипулирование данными с плавающей запятой внешней архитектуры ЦП, например, принятие VAX G_FLOATS из сетевого соединения и преобразование их в IEEE 754 long reals для обработки.
  • Обеспечение прямого доступа к битам для типа более высокого уровня.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }

С этим объявлением просто отобразить шестнадцатеричные байтовые значения long double, изменить знак показателя степени, определить, является ли это ненормальным значением, или реализовать длинную двойную арифметику для ЦП, который неподдерживать его и т. д.

  • Экономия места на диске, когда поля зависят от определенных значений:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  castrated;   // for males  
            int   pregnancies; // for females  
        } gender_specific_data;
    }
    
  • Grep включаемые файлыдля использования с вашим компилятором.Вы найдете десятки или сотни вариантов использования union:

    [wally@zenetfedora ~]$ cd /usr/include
    [wally@zenetfedora include]$ grep -w union *
    a.out.h:  union
    argp.h:   parsing options, getopt is called with the union of all the argp
    bfd.h:  union
    bfd.h:  union
    bfd.h:union internal_auxent;
    bfd.h:  (bfd *, struct bfd_symbol *, int, union internal_auxent *);
    bfd.h:  union {
    bfd.h:  /* The value of the symbol.  This really should be a union of a
    bfd.h:  union
    bfd.h:  union
    bfdlink.h:  /* A union of information depending upon the type.  */
    bfdlink.h:  union
    bfdlink.h:       this field.  This field is present in all of the union element
    bfdlink.h:       the union; this structure is a major space user in the
    bfdlink.h:  union
    bfdlink.h:  union
    curses.h:    union
    db_cxx.h:// 4201: nameless struct/union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:  union
    elf.h:typedef union
    _G_config.h:typedef union
    gcrypt.h:  union
    gcrypt.h:    union
    gcrypt.h:    union
    gmp-i386.h:  union {
    ieee754.h:union ieee754_float
    ieee754.h:union ieee754_double
    ieee754.h:union ieee854_long_double
    ifaddrs.h:  union
    jpeglib.h:  union {
    ldap.h: union mod_vals_u {
    ncurses.h:    union
    newt.h:    union {
    obstack.h:  union
    pi-file.h:  union {
    resolv.h:   union {
    signal.h:extern int sigqueue (__pid_t __pid, int __sig, __const union sigval __val)
    stdlib.h:/* Lots of hair to allow traditional BSD use of `union wait'
    stdlib.h:  (__extension__ (((union { __typeof(status) __in; int __i; }) \
    stdlib.h:/* This is the type of the argument to `wait'.  The funky union
    stdlib.h:   causes redeclarations with either `int *' or `union wait *' to be
    stdlib.h:typedef union
    stdlib.h:    union wait *__uptr;
    stdlib.h:  } __WAIT_STATUS __attribute__ ((__transparent_union__));
    thread_db.h:  union
    thread_db.h:  union
    tiffio.h:   union {
    wchar.h:  union
    xf86drm.h:typedef union _drmVBlank {
    
10 голосов
/ 02 марта 2013

Пример, когда я использовал объединение:

class Vector
{
        union 
        {
            double _coord[3];
            struct 
            {
                double _x;
                double _y; 
                double _z;
            };

        };
...
}

это позволяет мне получить доступ к моим данным в виде массива или элементов.

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

   union {   // dimension from left to right   // union for the left to right dimension
        uint32_t            m_width;
        uint32_t            m_sizeX;
        uint32_t            m_columns;
    };

    union {   // dimension from top to bottom   // union for the top to bottom dimension
        uint32_t            m_height;
        uint32_t            m_sizeY;
        uint32_t            m_rows;
    };
7 голосов
/ 25 января 2011

Союзы обеспечивают полиморфизм в C.

6 голосов
/ 25 января 2011

Ключевое слово union, все еще используемое в C ++ 03 1 , в основном является остатком C-дней.Наиболее очевидная проблема заключается в том, что он работает только с POD 1 .

. Однако идея объединения все еще присутствует, и библиотеки Boost действительно имеют класс, подобный объединению:

boost::variant<std::string, Foo, Bar>

Который имеет большинство преимуществ union (если не все) и добавляет:

  • способность правильно использовать типы не POD
  • безопасность статического типа

На практике было продемонстрировано, что она эквивалентна комбинации union + enum, и было отмечено, что она была такой же быстрой (в то время как boost::any большеобласть dynamic_cast, поскольку она использует RTTI).

1 Союзы были обновлены в C ++ 11 ( неограниченные союзы ) и могуттеперь содержат объекты с деструкторами, хотя пользователь должен вызывать деструктор вручную (на текущем активном члене объединения).Все еще намного проще использовать варианты.

...