什么时候有人会使用工会?它是仅限C天的残余吗?

Rus*_*sel 126 c c++ unions

我已经学会了但并没有真正得到工会.我经历的每一个C或C++文本都会介绍它们(有时会传递),但是它们往往只提供很少的实际例子来说明为什么或在哪里使用它们.工会什么时候可以用于现代(甚至遗留)案件?我只有两个猜测是编程微处理器,当你的空间非常有限,或者你正在开发一个API(或类似的东西),并且你想迫使最终用户只有一个对象/类型的实例一度.这两个猜测是否接近正确?

vz0*_*vz0 100

联合通常与鉴别器公司一起使用:一个变量,指示联合的哪个字段有效.例如,假设您要创建自己的Variant类型:

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;
    };
};
Run Code Online (Sandbox Code Playgroud)

然后你会使用它,如:

/* 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;
    ...
    }
}
Run Code Online (Sandbox Code Playgroud)

这实际上是一个非常常见的习惯用法,特别是在Visual Basic内部.

有关实际示例,请参阅SDL的SDL_Event联合.(这里是实际的源代码).type联合顶部有一个字段,每个SDL_*Event结构上都会重复相同的字段.然后,要处理正确的事件,您需要检查type字段的值.

好处很简单:只需一种数据类型即可处理所有事件类型,而无需使用不必要的内存.

  • @Russel C++类不能在C程序中使用,但可以使用'extern"C"'块从C++轻松地加入C结构/联合. (10认同)
  • 大!在那种情况下,我现在想知道为什么Sdl函数不仅仅是作为类层次结构实现的.这是为了使C兼容,而不仅仅是C++? (2认同)

jrs*_*ala 81

我发现C++联盟非常酷.似乎人们通常只考虑用户想要"就地"更改联合实例的值的用例(这看起来只是为了节省内存或执行可疑的转换).

事实上,即使你永远不会改变任何联合实例的价值,工会也可以作为一种软件工程工具.

用例1:变色龙

使用联合,您可以在一个面额下重新组合多个任意类,这与基类及其派生类的情况并非没有相似之处.但是,对于给定的union实例,您可以做什么和不能做什么改变:

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?
}
Run Code Online (Sandbox Code Playgroud)

看起来程序员在想要使用它时必须确定给定联合实例的内容类型.这是上述功能的情况f.但是,如果一个函数接收一个union实例作为传递的参数,就像g上面那样,那么它就不知道如何处理它.这同样适用于返回union实例的函数,请参阅h:调用者如何知道其中的内容?

如果一个union实例永远不会作为参数或返回值传递,那么它必然会有一个非常单调的生命,当程序员选择更改其内容时会引起兴奋:

Batman bm = {/* */};
Baseball bb = {/* */};
Bat b;
b.brucewayne = bm;
// stuff
b.club = bb;
Run Code Online (Sandbox Code Playgroud)

这是工会中最常见的(非)流行用例.另一个用例是当一个union实例出现一些告诉你它的类型的东西时.

用例2:"很高兴见到你,我object来自Class"

假设一个程序员选择总是将一个联合实例与一个类型描述符配对(我将把它留给读者自行决定想象一个这样的对象的实现).如果程序员想要的是节省内存并且类型描述符的大小相对于联合的大小不可忽略,那么这就破坏了联合本身的目的.但是,让我们假设,联合实例可以作为参数或返回值传递,而被调用者或调用者不知道内部是什么,这一点至关重要.

然后程序员必须编写一个switch控制流程声明来告诉Bruce Wayne除了木棍或类似的东西.当联盟中只有两种类型的内容时,这并不是太糟糕,但很明显,联盟不再扩展.

用例3:

正如ISO C++标准推荐书的作者在2008年提出的那样,

许多重要的问题域需要大量对象或有限的内存资源.在这些情况下,节约空间非常重要,而工会通常是完美的方式.实际上,一个常见的用例是union在其生命周期内永远不会更改其活动成员的情况.它可以被构造,复制和销毁,就像它只是一个只包含一个成员的结构一样.这种情况的典型应用是创建不动态分配的不相关类型的异构集合(可能它们是在映射中就地构建的,或者是数组的成员).

现在,一个例子,带有UML类图:

A类的许多成分

简单英语中的情况:A类的对象可以包含B1,...,Bn中的任何类的对象,并且每种类型中最多只有一个,其中n是一个相当大的数字,比如说至少10.

我们不希望将字段(数据成员)添加到A,如下所示:

private:
    B1 b1;
    .
    .
    .
    Bn bn;
Run Code Online (Sandbox Code Playgroud)

因为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
};
Run Code Online (Sandbox Code Playgroud)

然后,要从中获取联合实例的内容data,您使用a.get(TYPE_B2).b2和喜欢,其中a是一个类A实例.

由于工会在C++ 11中不受限制,因此功能更强大.有关详细信息,请参阅上面链接的文档本文.


小智 36

一个例子是嵌入式领域,其中寄存器的每个位可能意味着不同的东西.例如,8位整数和具有8个独立1位位域的结构的并集允许您更改一位或整个字节.

  • 我不会称之为"不推荐".在嵌入式空间中,它通常比替代方案更清晰,更不容易出错,通常要么涉及大量的显式转换,要么使用"void*"或掩码和移位. (11认同)
  • 这在设备驱动程序中也很常见.几年前,我使用这样的联合为一个项目编写了一个代码的_lot_代码.通常不推荐使用它,在某些情况下它可能是编译器特定的,但它可以工作. (7认同)

Jos*_*sey 23

Herb Sutter大约六年前在GOTW写道,重点是:

"但是,不要认为工会只是早期的保留.工会可能最有用的是通过允许数据重叠来节省空间,这在C++和当今的现代世界中仍然是可取的.例如,一些最高级C++世界上的标准库实现现在只使用这种技术来实现"小字符串优化",这是一个很好的优化替代方案,它在字符串对象本身内重用存储:对于大字符串,字符串对象内的空间存储通常指向动态的指针分配缓冲区和内务处理信息,如缓冲区的大小; 对于小字符串,相反的空间被重用来直接存储字符串内容并完全避免任何动态内存分配.有关小字符串优化(以及相当深度的其他字符串优化和悲观)的更多信息,请参阅...."

对于一个不太有用的例子,请参阅gcc,严格别名和通过联合转换的长但不确定的问题.


小智 20

好吧,我能想到的一个用例是:

typedef union
{
    struct
    {
        uint8_t a;
        uint8_t b;
        uint8_t c;
        uint8_t d;
    };
    uint32_t x;
} some32bittype;
Run Code Online (Sandbox Code Playgroud)

然后,您可以访问该32位数据块的8位独立部分; 然而,准备可能被字节序所咬.

这只是一个假设的例子,但是只要你想将字段中的数据拆分成这样的组件部分,就可以使用union.

也就是说,还有一种endian-safe的方法:

uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;
Run Code Online (Sandbox Code Playgroud)

例如,由于该二进制操作将由编译器转换为正确的字节序.


Yee*_*Fei 14

在处理字节级(低级)数据时,联合非常有用.

我最近的用法之一是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;
Run Code Online (Sandbox Code Playgroud)

  • 但请记住,访问这样的原始内容不是标准的,并且可能无法按预期与所有编译器一起工作. (7认同)
  • 此外,通常会以不保证对齐的方式使用它,这是未定义的行为. (3认同)

wal*_*lyk 12

工会的一些用途:

  • 为未知的外部主机提供通用字节顺序接口.
  • 处理外部CPU架构浮点数据,例如从网络链接接受VAX G_FLOATS并将它们转换为IEEE 754长实数进行处理.
  • 提供对更高级别类型的直接位琐事访问.
union {
      unsigned char   byte_v[16];
      long double     ld_v;
 }
Run Code Online (Sandbox Code Playgroud)

使用此声明,可以很容易地显示a的十六进制字节值long double,更改指数的符号,确定它是否为非正规值,或者为不支持它的CPU实现长双精度算法等.

  • 当字段依赖于某些值时节省存储空间:

    class person {  
        string name;  
    
        char gender;   // M = male, F = female, O = other  
        union {  
            date  vasectomized;  // for males  
            int   pregnancies;   // for females  
        } gender_specific_data;
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 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 {
    
    Run Code Online (Sandbox Code Playgroud)

  • Tsk tsk!两个downvotes,没有解释.这令人失望. (4认同)
  • 我猜你得到了"阉割"或"怀孕"联盟的支持.有点不舒服. (4认同)
  • 是的,我猜那天是黑暗的一天。 (4认同)

Dan*_*nyK 10

我使用联合的一个例子:

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

        };
...
}
Run Code Online (Sandbox Code Playgroud)

这允许我以数组或元素的形式访问我的数据.

我使用了一个联合使不同的术语指向相同的值.在图像处理中,无论我是在列或宽度上工作还是在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;
    };
Run Code Online (Sandbox Code Playgroud)

  • 请注意,尽管此解决方案适用于大多数可观察平台,但将值设置为_x,_y,_z并访问_coord是一种未定义的行为.工会的主要目的是保留空间.您必须访问与先前设置的完全相同的union元素. (11认同)

Nul*_*Set 7

联合会在C中提供多态性.

  • 我以为`void*`做了那个^^ (17认同)
  • @ user166390 Polymorphism使用相同的接口来操作多个类型; void*没有接口. (2认同)
  • 在 C 中,多态性通常通过不透明类型和/或函数指针来实现。我不知道您如何或为什么要使用工会来实现这一目标。这听起来真是个坏主意。 (2认同)

Mat*_* M. 6

union关键字虽然仍在C++ 03 1中使用,但主要是C天的残余.最明显的问题是它只适用于POD 1.

然而,联合的想法仍然存在,实际上Boost库具有类似联合的类:

boost::variant<std::string, Foo, Bar>
Run Code Online (Sandbox Code Playgroud)

这具有union(如果不是全部)的大部分好处并增加:

  • 正确使用非POD类型的能力
  • 静态型安全

在实践中,已经证明它相当于union+ 的组合enum,并且基准测试它的速度一样快(因为它使用RTTI,因此boost::any更多的是领域dynamic_cast).

1在C++ 11(不受限制的联合)中升级了联合,现在可以包含具有析构函数的对象,尽管用户必须手动调用析构函数(在当前活动的联合成员上).使用变体仍然容易得多.


Shu*_*rma 6

联合的一个很好的用法是内存对齐,我在PCL(Point Cloud Library)源代码中找到了它.API中的单个数据结构可以针对两种架构:支持SSE的CPU以及没有SSE支持的CPU.例如:PointXYZ的数据结构是

typedef union
{
  float data[4];
  struct
  {
    float x;
    float y;
    float z;
  };
} PointXYZ;
Run Code Online (Sandbox Code Playgroud)

3个浮子用另外的浮子填充,用于SSE对齐.因此对于

PointXYZ point;
Run Code Online (Sandbox Code Playgroud)

用户可以访问point.data [0]或point.x(取决于SSE支持)来访问say,x坐标.更多类似的更好的使用细节在以下链接:PCL文档PointT类型


归档时间:

查看次数:

69928 次

最近记录:

6 年,1 月 前