在C ++中使用联合是一种好习惯吗?

时间:2009-06-03 06:00:00

标签: c++

我需要定义一个这样的类:

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

private:
    Data m_data;
};

另一种方法当然是将数据定义为整数,并在必要时将其转换为char数组。

我想知道哪一个是首选方式。这里的矛盾是我有一个人的远程记忆提醒不再使用联合,但在这种情况下它似乎是一个更清洁的解决方案。

12 个答案:

答案 0 :(得分:22)

只要您使用小心,工会就可以了。

它们可以以两种方式使用:

  1. 允许以多种方式访问​​单一类型的数据(如在您的示例中,将颜色作为int或(您可能想要的)访问四个字符)

  2. 制作多态类型(例如,可以包含int或float的单个值)。

  3. 案例(1)很好,因为你没有改变类型的含义 - 你可以读取和写入联盟的任何成员而不会破坏任何东西。这使得它以一种非常方便有效的方式以稍微不同的形式访问相同的数据。

    案例(2)可能很有用,但是非常危险,因为您需要始终从联合中访问正确类型的数据。如果你写一个int并尝试将它作为一个浮点数读回来,你将得到一个毫无意义的值。除非主要考虑使用内存,否则最好使用一个包含两个成员的简单结构。

    以前,联盟在C语言中至关重要。在C ++中,通常有更好的方法来实现相同的目的(例如,一个类可以用来包装一个值并允许以不同的方式访问它)。但是,如果您需要原始性能或存在严重的内存情况,那么工会仍然是一种有用的方法。

答案 1 :(得分:7)

这是好的做法吗?是的,但有一些警告。

在记忆稀缺的日子里,工会很容易重复使用记忆。那些日子早已过去,为此目的使用工会增加了不必要的复杂性。不要这样做。

如果一个工会真正描述了你的数据,就像你给出的例子那样,那么这是完全合理的事情。但是,请注意,您正在构建一些平台依赖项。在具有不同整数大小或不同字节顺序的不同平台上,您可能无法得到您期望的结果。

答案 2 :(得分:6)

在C ++中,联盟的使用受到其成员必须是POD(普通旧数据)这一事实的限制。例如,union成员不能有构造函数或析构函数,以及其他限制。

答案 3 :(得分:3)

如果按照您描述的方式使用,此代码将是未定义的行为。

在C ++中,任何时候只有最后写入的联合成员活动。访问其他成员就像访问未初始化的变量一样。

有关深入讨论,请参阅this thread

工会可能用于节省空间;例如实现变体。它们可能不会用于打字。

答案 4 :(得分:2)

使用工会仍然是可以接受的做法。只需将rgbBytes更改为数组:)

在C中,工会可用于不同目的。有时它们被用作Variant类型,即在同一存储位置保存不同类型的值。这种用法在C ++中是有问题的,因为你使用继承/多态。但是,联合的另一个用途是为相同的数据提供不同的“接口”。这种用法对C ++仍然有效。

答案 5 :(得分:2)

由于您使用的是C ++,我认为这不是一个好习惯。如果你只限于纯C,那肯定不会。

最大的问题是,union的大小总是最大的“成员”的大小,所以如果你想存储一个字节或一个shitloadofdata,大小是sizeof(shitloadofdata)而不是一个字节。 / p>

多态性是比工会更好的选择。

答案 6 :(得分:2)

在我假设的C ++编码标准中,工会将被禁止,因为它们往往违反了“正确性,简单性和清晰度优先”的规则。

然而,这并不是普遍的建议,据我记得,Sutter和Alexandrescu并没有在他们的C++ Coding Standards中统治他们。

幸运的是,我认识的每个人都发现他们很难做到他们没有产生它们。如果只有他们在API中找到了void *,那么也很难做到:)

答案 7 :(得分:2)

我不喜欢工会的事情是他们是无差别的;他们没有提供关于当前基础类型的信息,并且通过访问联合的错误方面来违反类型安全非常容易。

Boost::variant解决了很多这些问题。正如文档所指出的那样,union在“面向对象的环境中几乎无用”,而boost :: variant提供了一种非常面向对象的方法来解决实际的并集问题。它的接口被设计为不允许访问变体,除非您使用正确的类型,并且如果联合扩展为包含您不期望的类型,它们提供的“访问者”模式示例会给出编译时错误。

至于它是否有用;我认同。我已经将它们用于简单的大型接口

 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足够长,我不想内联它)

答案 8 :(得分:0)

Keep in mind that the C++11 standard says that usage is undefined behavior, see M.M's answer (which is IMHO the best answer here). Future standards might define it, but still multibyte number can be stored in big or little endian way. So you should not use unions neither type casting for type puning if portability is an issue.

If it is not an issue, let me try to upgrade your code a bit to show how the unions might find their place under the C++ sun.

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;}
};

Explanation

  • struct inside union should have name: ISO C++ prohibits anonymous structs, however g++ would compile them
  • other constructors delegates to default constructor to copy values, not references (C++11)
  • the third constructor is here for comfortable implicit call
  • the construct assumes that multibyte numbers are stored in LSB order

Usage

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

答案 9 :(得分:-1)

这绝对是C ++中对联合的有效使用。根据您的目的,您可以将类更改为联合,这样您就不会进行嵌套。联盟可以拥有方法并使用继承。如果那是不可能的(还有其他数据成员),那么你可能想要使用这样的匿名联合:

class Color
{
private:
   union
   {
       unsigned int intValue;
       unsigned char argbBytes[4];
   };
public:
    unsigned int GetIntValue() { return intValue; }
};

答案 10 :(得分:-2)

是的,使用联合是绝对好的做法 - 这是告诉编译器一块内存用于存储不同类型的唯一方法。 使用联合保持静态类型安全,因为没有reinterpret_cast&lt;&gt;需要使用和 使代码的意图更容易阅读。

在使用严格别名进行编译时,还必须使用联合,在这种情况下,编译器将假定指向不同类型的指针永远不会指向同一内存。严格别名本身就是一个主题,但是当启用严格别名时,通过不同的指针类型对同一内存进行简短的读/写操作通常不会按预期运行。

答案 11 :(得分:-2)

如果您正在使用GCC并且要引用指向相同位置的指针,那么最好坚持使用联盟。

请考虑以下代码:

int main() {
    char rgba[4] = {0xCC, 0xCC, 0xCC, 0};
    int *value = (int*)&rgba;
}

使用-Wstrict-aliasing = 2编译此代码将发出警告,说明违反了严格的别名规则。访问值是未定义的行为。另一方面,使用union来访问另一个变量的某些部分并不违反严格的别名规则。