什么时候会有人使用工会? 这只是C天的残余吗?
我已经学会了,但并没有真正得到工会。 我经历的每一个C或C ++文本都会介绍它们(有时候会传递),但是它们往往只给出很less的实际例子,说明为什么或者在哪里使用它们。 什么时候工会在现代(甚至是遗留)案件中有用? 我只有两个猜测是编程微处理器,当你有非常有限的空间工作,或者当你正在开发一个API(或类似的东西),你想强迫最终用户只有一个对象/types的实例一次。 这两个猜测是否接近正确?
工会通常与鉴别者的公司一起使用:一个variables表示工会的哪个领域是有效的。 例如,假设你想创build你自己的Varianttypes:
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联合 。 ( 实际的源代码在这里 )。 联合体顶部有一个type
字段,每个SDL_ * Event结构体都重复相同的字段。 然后,要处理正确的事件,您需要检查type
字段的值。
好处很简单:有一种数据types可以处理所有事件types,而不需要使用不必要的内存。
我发现C ++工会很酷。 看起来人们通常只会想到一个用例,在这个用例中,“想要”改变联合实例的值(这似乎只是为了节省内存或执行可疑的转换)。
事实上, 即使你永远不会改变任何union实例的价值 ,工会也可以作为一个软件工程工具。
用例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? }
看来,程序员必须确定一个给定的联合实例的内容types,当他想要使用它。 上面的函数f
就是这种情况。 但是,如果一个函数接收到一个联合实例作为传递的参数,就像上面的g
,那么它就不知道该怎么做。 这同样适用于返回union实例的函数,请参阅h
:调用者如何知道里面的内容?
如果一个联合实例永远不会作为参数或返回值传递,那么它肯定会有一个非常单调的生活,当程序员select改变它的内容时会激起兴奋的尖峰:
Batman bm = {/* */}; Baseball bb = {/* */}; Bat b; b.brucewayne = bm; // stuff b.club = bb;
这是最受欢迎的工会使用案例。 另一个用例是当一个union实例出现一些告诉你的types时。
使用案例2:“很高兴认识你,我object
,从Class
”
假设一个程序员select总是将一个联合实例与一个types描述符配对(我将让读者自行决定想象一个这样的对象的实现)。 如果程序员想要的是节省内存,而且联合的types描述符的大小不可忽略,那么这就违背了联合本身的目的。 但是让我们假设联合实例可以作为parameter passing或作为返回值传递给被调用者或调用者不知道里面是什么是至关重要的。
然后,程序员必须写一个switch
控制stream程语句,以告诉Bruce Wayne与木棍等等。 如果联盟中只有两种types的内容,这并不算什么坏事,但很显然,这个联盟不再可以扩展。
用例3:
正如ISO C ++标准build议的作者于2008年所回顾的,
许多重要的问题领域需要大量的对象或有限的内存资源。 在这种情况下,节约空间是非常重要的,工会往往是完成这一任务的最佳途径。 事实上,一个常见的使用案例是工会在其整个生命周期中从未改变其活跃成员的情况。 它可以被构造,复制和析构,就像它是一个只包含一个成员的结构一样。 这样做的一个典型应用是创build一个不相关types的异类集合,这些types不是dynamic分配的(也许它们是在地图或数组成员中就地构造的)。
现在,用UML类图表示一个例子:
简单英语的情况:A类的对象可以具有B1,…,Bn中的任何类别的对象,并且每个types中最多只有一个types,其中n是相当大的数字,至less是10。
我们不想像这样将字段(数据成员)添加到A:
private: B1 b1; . . . Bn bn;
因为n可能会有所不同(我们可能想要将Bx类添加到混合中),并且因为这会造成构造函数的混乱,并且因为A对象会占用大量空间。
我们可以使用古怪的void*
指针容器来指向包含对象的Bx
对象来检索它们,但是这样做很麻烦,所以C风格…但更重要的是,这将使我们处理许多dynamic分配对象的生命周期。
相反,可以做的是这样的:
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中是不受限制的。 有关详细信息,请参阅链接到上面或本文 的文档 。
embedded领域就是一个例子,寄存器的每一位可能意味着不同的东西。 例如,一个8位整数和一个具有8个独立的1位位域的结构的联合允许你改变一位或整个字节。
大约六年前, GutW写了香草Sutter , 着重补充说:
“但是,不要认为工会只是一个早期的保留,工会可能通过允许数据重叠来节省空间, 这在C ++和当今的现代世界中仍然是可取的,例如,一些最世界上先进的C ++标准库实现现在只使用这种技术来实现“小string优化”,这是一个很好的优化select,它重用string对象本身的存储空间:对于大string,string对象中的空间存储通常的指针dynamic分配的缓冲区和pipe理信息,如缓冲区的大小;对于小string,相同的空间被重用来直接存储string内容,并完全避免任何dynamic的内存分配;关于小string优化(和其他string优化和相当深度的悲观情绪),请看……“
而对于一个不太有用的例子,看到一个长而不确定的问题gcc,严格别名,并通过联合投射 。
那么,我能想到的一个示例用例是这样的:
typedef union { struct { uint8_t a; uint8_t b; uint8_t c; uint8_t d; }; uint32_t x; } some32bittype;
然后可以访问该32位数据块的8位独立部分; 然而,准备可能会被字节码咬伤。
这只是一个假设的例子,但是只要你想将字段中的数据拆分成像这样的组成部分,就可以使用联合。
也就是说,还有一种方法是endian-safe:
uint32_t x; uint8_t a = (x & 0xFF000000) >> 24;
例如,由于该二进制操作将由编译器转换为正确的字节顺序。
处理字节级(低级)数据时,联合是有用的。
我最近的使用之一是IP地址build模,如下所示:
// 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;
我使用联合的一个例子:
class Vector { union { double _coord[3]; struct { double _x; double _y; double _z; }; }; ... }
这允许我以数组或元素的forms访问我的数据。
我使用了一个联合来使不同的术语指向相同的值。 在image processing中,无论是在列方向还是宽度方向,还是在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; };
工会的一些用途:
- 为一个未知的外部主机提供一个通用的sorting界面。
- 操纵外部CPU架构浮点数据,例如接受来自networking链接的VAX G_FLOATS并将其转换为IEEE 754 long reals进行处理。
- 提供直接的比特级访问到更高级别的types。
union { unsigned char byte_v[16]; long double ld_v; }
使用这个声明,很容易显示一个
long double
的hex字节值,改变指数的符号,确定它是否是一个非正规值,或者对不支持它的CPU执行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 {
联盟在C中提供多态性
union
关键字虽然仍然用于C ++ 03 1 ,但是大部分是C日的剩余部分。 最明显的问题是,它只适用于POD 1 。
但是,这个联盟的想法仍然存在,Boost图书馆的确拥有一个类似于工会的阶级:
boost::variant<std::string, Foo, Bar>
union
大部分好处(如果不是全部的话),并补充说:
- 正确使用非PODtypes的能力
- 静态型安全
在实践中,已经certificate它相当于union
+ enum
的组合,并且基准它是一样快(而boost::any
更多的是dynamic_cast
的领域,因为它使用了RTTI)。
虽然用户必须手动调用析构函数(在当前活动的联合成员上),但是在C ++ 11( unrestricted union )中升级了联合 ,现在可以包含具有析构函数的对象。 使用变体还是容易得多。
联合的一个很好的用法是内存alignment,我在PCL(点云库)源代码中find。 API中的单一数据结构可以针对两种体系结构:支持SSE的CPU以及没有SSE支持的CPU。 例如:PointXYZ的数据结构是
typedef union { float data[4]; struct { float x; float y; float z; }; } PointXYZ;
SSEalignment时,3个浮标用另外的浮标填充。 因此对于
PointXYZ point;
用户可以访问point.data [0]或point.x(取决于SSE支持)来访问say,x坐标。 更多类似更好的使用细节在以下链接: PCL文档PointTtypes
从维基百科关于工会的文章 :
一个工会的主要用途是节约空间 ,因为它提供了让许多不同types存储在同一个空间的方法。 联盟也提供粗糙的多态性 。 然而,没有types的检查,所以由程序员来确定在不同的上下文中访问适当的字段。 联合variables的相关字段通常由其他variables的状态确定,可能在封闭结构中。
一个常见的C编程习惯用法是使用联合来执行C ++调用reinterpret_cast的方法,方法是分配给一个联合的一个字段,并从另一个字段读取,就像在取决于值的原始表示的代码中所做的那样。
假设你有n种不同types的configuration(只是一组定义参数的variables)。 通过使用configurationtypes的枚举,可以定义一个具有configurationtypesID的结构,以及所有不同typesconfiguration的联合。
这样,只要你通过configuration就可以使用ID来确定如何解释configuration数据,但是如果configuration是巨大的,你不会被迫为每个潜在的types浪费空间的并行结构。
最近版本的C标准中引入的严格的混淆规则给出了最近对于工会已经提升的重要性的推动。
你可以使用工会来打字,而不违反C标准。
这个程序有未指定的行为 (因为我假设float
和unsigned int
具有相同的长度),但没有未定义的行为 (请参阅此处 )。
#include <stdio.h> union float_uint { float f; unsigned int ui; }; int main() { float v = 241; union float_uint fui = {.f = v}; //May trigger UNSPECIFIED BEHAVIOR but not UNDEFINED BEHAVIOR printf("Your IEEE 754 float sir: %08x\n", fui.ui); //This is UNDEFINED BEHAVIOR as it violates the Strict Aliasing Rule unsigned int* pp = (unsigned int*) &v; printf("Your IEEE 754 float, again, sir: %08x\n", *pp); return 0; }
我想添加一个很好的实例来说明如何使用联合实现的公式计算器/解释器或者在计算中使用某种计算器(例如,您希望在计算公式的运行时部分使用可修改的方法 – 只需数字地求解方程 -例如)。 所以你可能想定义不同types的数字/常量(整数,浮点数,甚至复数),如下所示:
struct Number{ enum NumType{int32, float, double, complex}; NumType num_t; union{int ival; float fval; double dval; ComplexNumber cmplx_val} }
所以你要节省内存,更重要的是 – 你可以避免dynamic分配可能的极端数量(如果你使用大量运行时定义的数字)的小对象(与通过类inheritance/多态的实现相比)。 但是更有趣的是,你仍然可以使用这种types的结构体来使用C ++多态性(例如,如果你是双重调度的粉丝)。 只需将“虚拟”接口指针添加到所有数字types的父类中作为此结构的字段,指向此实例,而不是/除了原始types之外,或者使用良好的旧C函数指针。
struct NumberBase { virtual Add(NumberBase n); ... } struct NumberInt: Number { //implement methods assuming Number's union contains int NumberBase Add(NumberBase n); ... } struct NumberDouble: Number { //implement methods assuming Number's union contains double NumberBase Add(NumberBase n); ... } //etc for all number types/or use templates struct Number: NumberBase{ union{int ival; float fval; double dval; ComplexNumber cmplx_val;} NumberBase* num_t; Set(int a) { ival=a; //still kind of hack, hope it works because derived classes of Number dont add any fields num_t = static_cast<NumberInt>(this); } }
所以你可以使用多态而不是使用switch(type)进行types检查 – 使用内存有效的实现(不需要dynamic分配小对象) – 当然,如果你需要的话。
从http://cplus.about.com/od/learningc/ss/lowlevel_9.htm :
工会的用途很less。 在大多数计算机上,指针和int的大小通常是相同的 – 这是因为两者通常都适合CPU中的寄存器。 所以如果你想做一个int指针的快速和脏的转换或者其他的方式,声明一个联合。
union intptr { int i; int * p; }; union intptr x; xi = 1000; /* puts 90 at location 1000 */ *(xp)=90;
联合的另一个用途是在一个命令或消息协议中发送和接收不同大小的消息。 每个消息types将保存不同的信息,但每个消息都将有一个固定的部分(可能是一个结构)和一个可变的部分位。 这是你如何实现它..
struct head { int id; int response; int size; }; struct msgstring50 { struct head fixed; char message[50]; } struct
struct msgstring80 {struct head fixed; char消息[80]; }
struct msgint10 {struct head fixed; int message [10]; } struct msgack {struct head fixed; int ok; }联合消息types{
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }实际上,虽然工会的规模相同,但只发送有意义的数据而不浪费空间是有意义的。 msgack的大小只有16个字节,而msgstring80的大小是92个字节。 所以当一个消息typesvariables被初始化的时候,它的大小字段就根据它的types来设置。 这可以被其他函数用来传输正确的字节数。
联盟提供了一种方法来处理单个存储区域中的不同types的数据,而不在程序中embedded任何独立于机器的信息。它们类似于pascal中的变体logging
作为一个例子,可以在编译器符号表pipe理器中find,假设一个常量可以是一个int,一个浮点数或一个字符指针。 特定常量的值必须存储在适当types的variables中,但如果值占用相同的存储量并存储在同一个地方而不pipe其types如何,对于表pipe理来说是最方便的。 这是一个联合的目的 – 一个单一的variables,可以合法地持有几种types的任何一种。 语法基于结构:
union u_tag { int ival; float fval; char *sval; } u;
variablesu将足够大以保持三种types中最大的一种; 具体的大小是依赖于实现的。 只要用法一致,任何这些types都可以被分配给u,然后用于expression式中