Это хорошая практика, чтобы использовать союзы в C ++? - PullRequest
22 голосов
/ 03 июня 2009

Мне нужно определить класс следующим образом:

class Color
{
private:
   union Data
   {
       unsigned int intValue;
       unsigned char argbBytes[4];
   }

private:
    Data m_data;
};

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

Мне интересно, какой из них предпочтителен. Противоречие здесь состоит в том, что у меня есть удаленная память о чьем-то напоминании о том, чтобы больше не использовать union, однако в данном случае это более чистое решение.

Ответы [ 12 ]

22 голосов
/ 03 июня 2009

Союзы могут быть хорошими, если вы используете их осторожно .

Их можно использовать двумя способами:

  1. Чтобы разрешить доступ к одному типу данных несколькими способами (как в вашем примере, доступ к цвету в виде целого или (как вы, вероятно, предполагали) четырех символов)

  2. Для создания полиморфного типа (например, одного значения, которое может содержать int или float).

Случай (1) Хорошо, потому что вы не меняете значение типа - вы можете читать и писать любому члену объединения, ничего не нарушая. Это делает его очень удобным и эффективным способом доступа к одним и тем же данным в несколько разных формах.

Случай (2) может быть полезен, но чрезвычайно опасен, потому что вам необходимо всегда получать доступ к нужному типу данных из объединения. Если вы напишите int и попытаетесь прочитать его обратно как float, вы получите бессмысленное значение. Если использование памяти не является вашим основным соображением, может быть лучше использовать простую структуру с двумя членами в ней.

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

7 голосов
/ 03 июня 2009

Это хорошая практика? Да, но с некоторыми оговорками.

В те времена, когда памяти было мало, профсоюзы были популярны для повторного использования памяти. Эти дни давно прошли, и использование профсоюзов для этой цели добавляет ненужной сложности. Не делай этого.

Если профсоюз действительно описывает ваши данные, как это делается в приведенном вами примере, то это вполне разумная вещь. Тем не менее, имейте в виду, что вы строите некоторые зависимости платформы. На другой платформе с разными целочисленными размерами или другим порядком байтов вы можете не получить то, что ожидали.

6 голосов
/ 03 июня 2009

В C ++ использование объединений ограничено тем фактом, что их членами должны быть POD (простые старые данные). Например, член объединения не может иметь конструктора или деструктора, помимо других ограничений.

3 голосов
/ 18 июля 2015

Этот код будет неопределенным поведением, если использовать его так, как вы описали.

В C ++ только последний написанный член объединения является активным одновременно. Доступ к другим членам аналогичен доступу к неинициализированным переменным.

Подробное обсуждение см. в этой теме .

.

Союзы могут быть использованы для экономии места; например реализация варианта. Они не могут быть использованы для типа наказания.

2 голосов
/ 03 июня 2009

Что мне не нравится в профсоюзах, так это то, что они неразборчивы; они не дают никакой информации о том, каков базовый тип в настоящее время, и очень легко нарушить безопасность типов, обращаясь к неправильной стороне объединения.

Boost :: variable решает многие из этих проблем. Как указывается в документации, union является «почти бесполезным в объектно-ориентированной среде», в то время как boost :: variable дает очень объектно-ориентированный подход к решению практических задач объединения. Его интерфейс спроектирован так, чтобы не разрешать доступ к варианту, если вы не используете правильный тип, а предоставленный ими пример шаблона «посетитель» дает ошибки времени компиляции, если объединение расширено, чтобы включить тип, который вы не ожидали.

Как будто это полезно; Я думаю так. Я использовал их просто для больших интерфейсов

 class some_xml_class {
 public:
    void set_property(const string&, const string&);
    void set_property(const string&, const vector<string>&);
    void set_property(const string&, const set<string>&);
    void set_property(const string&, int);

    void set_super_property(const string&, const string&);
    void set_super_property(const string&, const vector<string>&);
    void set_super_property(const string&, const set<string>&);
    void set_super_property(const string&, int);

стихи

 class some_xml_class {
 public:
    typedef boost::variant<string, vector<string>, set<string>, int> property_type;
    void set_property(const string&, const property_type&);
    void set_super_property(const string&, const property_type&);

(здесь также могут быть полезны шаблоны, но допустим, что impl был достаточно длинным, я не хотел его вставлять)

2 голосов
/ 03 июня 2009

В моем гипотетическом C ++ стандарте кодирования профсоюзы будут запрещены , так как они, как правило, нарушают правило "правильности, простоты и ясности на первом месте".

Однако, это не распространенная рекомендация, и Саттер и Александреску, насколько я помню, не выносили решения против них в своих C ++ Стандартах кодирования .

К счастью, все, кого я знаю, находят их настолько трудными, чтобы понять, что они не производят их. Если бы только они нашли, что void * в API тоже трудно понять:)

2 голосов
/ 03 июня 2009

Поскольку вы используете C ++, я бы сказал, что это не очень хорошая практика. Если вы ограничены только чистым С, почему бы и нет.

Самая большая проблема imo заключается в том, что размер объединения всегда равен размеру самого большого «члена», поэтому, если вы хотите сохранить байт или shitloadofdata, размер будет sizeof (shitloadofdata), а не байт.

Полиморфизм - намного лучший вариант, чем союзы.

2 голосов
/ 03 июня 2009

Использование союзов все еще является приемлемой практикой. Просто измените rgbBytes на массив :)

В C, союзы могут быть использованы для различных целей. Иногда они используются в качестве типа Variant, то есть для хранения значений другого типа в одной и той же ячейке памяти. Такое использование было бы сомнительным в C ++, потому что вы бы использовали наследование / полиморфизм. Тем не менее, другое использование союзов заключается в предоставлении другого «интерфейса» для одних и тех же данных. Этот вид использования все еще действителен для C ++.

0 голосов
/ 25 марта 2018

Имейте в виду, что стандарт C ++ 11 гласит, что использование является неопределенным поведением, см. ответ М.М (который ИМХО является лучшим ответом здесь). Будущие стандарты могли бы определить это, но все же многобайтовое число может быть сохранено в порядке байтов или порядков байтов. Таким образом, вы не должны использовать ни союзы, ни приведение типов для определения типа, если переносимость является проблемой.

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

class Color {
    union {
        uint32_t value;
        struct {
            uint8_t b, g, r; // LSB value expected
        } part;
    } data;
public:
    uint32_t &value;
    uint8_t &r, &g, &b;
    Color()
        : value(data.value)
        , r(data.part.r)
        , g(data.part.g)
        , b(data.part.b)
    { value = 0; }
    Color(Color const& c) : Color() { value = c.value; }
    Color(uint32_t _value) : Color() { value = _value; }
    Color& operator=(Color const& c) { value = c.value;}
    Color& operator=(uint32_t _value) { value = _value;}
};

Объяснение

  • Структура внутри объединения должна иметь имя: ISO C ++ запрещает анонимные структуры, однако g ++ будет их компилировать
  • другие конструкторы делегируют конструктору по умолчанию для копирования значений, а не ссылок (C ++ 11)
  • третий конструктор здесь для удобного неявного вызова
  • конструкция предполагает, что многобайтовые числа хранятся в порядке LSB

Использование

Color c1 = 0xAABBCC; // implicit call
std::cout << hex << +c1.r << endl; // AA
Color c2 = c1; // copy constructor
c2.g = 127;
std::cout << hex << +c1.g << " " << +c2.g << endl; // BB 7F
c1 = 0xDEADCD; // assignment operator
std::cout << hex << +c1.g << " " << +c2.g << endl; // AD 7F
0 голосов
/ 06 июня 2009

Это абсолютно допустимое использование союзов в C ++. В зависимости от того, что вы хотите сделать, вы можете изменить свой класс на объединение, чтобы у вас не было вложенности. Профсоюзы могут иметь методы и использовать наследование. Если это невозможно (есть и другие члены данных), вы можете использовать анонимный союз, подобный этому:

class Color
{
private:
   union
   {
       unsigned int intValue;
       unsigned char argbBytes[4];
   };
public:
    unsigned int GetIntValue() { return intValue; }
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...