私は学んだが、実際には組合を取得しません。私が通るすべてのCまたはC++テキストはそれらを紹介しますが(時には合格します)、それらはそれらを使用する理由または場所の実際的な例をほとんど提供しない傾向があります。現代の(またはレガシーの)ケースで組合はいつ役に立つのでしょうか?私の唯一の推測は、作業するスペースが非常に限られている場合、またはAPI(または同様のもの)を開発していて、エンドユーザーに複数のオブジェクト/タイプのインスタンスを1つだけ持たせたい場合のマイクロプロセッサのプログラミングです一度。これらの2つの推測はさらに近いですか?
組合は通常、弁別者の会社で使用されます。組合のどのフィールドが有効であるかを示す変数です。たとえば、独自の 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;
};
};
次に、次のように使用します。
/* 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_ * Event構造体で同じフィールドが繰り返されます。次に、正しいイベントを処理するには、type
フィールドの値を確認する必要があります。
利点は簡単です。不要なメモリを使用せずにすべてのイベントタイプを処理する単一のデータタイプがあります。
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;
そして、それが最も一般的な(非)組合のユースケースです。別のユースケースは、ユニオンインスタンスがそのタイプを示す何かを伴う場合です。
object
、出身はClass
」プログラマが常にユニオンインスタンスと型記述子をペアにすることを選択したとします(そのようなオブジェクトの実装を想像するのは読者の裁量に任せます)。プログラマーがメモリを節約することを望んでおり、タイプ記述子のサイズがユニオンのサイズに対して無視できない場合、これはユニオン自体の目的を無効にします。しかし、ユニオンインスタンスを引数または戻り値として、呼び出し先または呼び出し元が内部の内容を知らない状態で渡すことができることが重要だとしましょう。
次に、プログラマーはswitch
制御フローステートメントを記述して、ブルースウェインに木製の棒または同等のものと区別する必要があります。ユニオンに2種類のコンテンツしかない場合でも、それほど悪くはありませんが、明らかに、ユニオンはもうスケーリングしません。
ISO C++標準の推奨 の著者が2008年にそれを元に戻したため、
多くの重要な問題領域には、多数のオブジェクトまたは限られたメモリリソースが必要です。これらの状況では、スペースを節約することが非常に重要であり、組合はそれを行うのに最適な方法です。実際、一般的なユースケースは、ユニオンがその存続期間中にアクティブメンバーを変更しないという状況です。 1つのメンバーのみを含む構造体であるかのように、構築、コピー、および破棄できます。これの典型的な用途は、動的に割り当てられない無関係なタイプの異種コレクションを作成することです(おそらく、それらはマップまたはインプレースのメンバーにインプレースで構築されます)。
次に、UMLクラス図を使用した例を示します。
わかりやすい英語の状況:クラスAのオブジェクト できる B1、...、Bn、および最大で各タイプのいずれかのクラスのオブジェクトを持ち、 n かなり大きい数で、少なくとも10と言います。
次のようにフィールド(データメンバー)をAに追加したくありません。
private:
B1 b1;
.
.
.
Bn bn;
nが異なる可能性があるため(ミックスにBxクラスを追加したい場合があります)、また、これによりコンストラクタが混乱し、Aオブジェクトが多くのスペースを確保します。
キャストを持つBx
オブジェクトへのvoid*
ポインターの奇抜なコンテナーを使用してそれらを取得することもできますが、それはfuいため、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では共用体が制限されていないため、これはさらに強力です。詳細については、 上記にリンクされているドキュメント または この記事 を参照してください。
1つの例は、組み込みレルムにあり、レジスタの各ビットが異なる意味を持つ場合があります。たとえば、8ビット整数と8つの個別の1ビットビットフィールドを持つ構造体を結合すると、1ビットまたはバイト全体を変更できます。
Herb Sutter 書き込み GOTW 約6年前、emphasis追加:
「しかし、ユニオンは以前からのホールドオーバーであるとは思わないでください。ユニオンは、おそらくデータをオーバーラップさせてスペースを節約するのに最も便利です。 C++および今日の現代世界で望ましい。たとえば、いくつかの最も先進的なC++世界の標準ライブラリの実装では、この手法のみを使用して、「小さな文字列の最適化」を実装しています。文字列オブジェクト内のスペースには、動的に割り当てられたバッファへの通常のポインタと、バッファのサイズなどのハウスキーピング情報が格納されます。小さな文字列の場合、代わりに同じスペースが再利用されて文字列の内容が直接格納され、動的メモリ割り当てが完全に回避されます。小さな文字列の最適化(および他の文字列の最適化とかなりの深さの悲観化)については、...を参照してください。
そして、あまり有用ではない例については、長くても決定的な質問 gcc、strict-aliasing、およびunionを介したキャスト を参照してください。
さて、私が考えることができる1つの使用例はこれです:
typedef union
{
struct
{
uint8_t a;
uint8_t b;
uint8_t c;
uint8_t d;
};
uint32_t x;
} some32bittype;
その後、32ビットのデータブロックの8ビットの個別の部分にアクセスできます。ただし、潜在的にエンディアンに噛まれることに備えてください。
これは仮想的な例の1つにすぎませんが、フィールド内のデータをこのようなコンポーネントパーツに分割する場合は、ユニオンを使用できます。
とはいえ、エンディアンに対して安全な方法もあります:
uint32_t x;
uint8_t a = (x & 0xFF000000) >> 24;
たとえば、そのバイナリ演算はコンパイラによって正しいエンディアンに変換されるためです。
ユニオンは、バイトレベル(低レベル)のデータを扱うときに役立ちます。
私の最近の使用法の1つは、以下のような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;
ユニオンの用途:
union { unsigned char byte_v[16]; long double ld_v; }
この宣言を使用すると、
long double
の16進バイト値の表示、指数の符号の変更、非正規値であるかどうかの判別、またはそれをサポートしない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 {
ユニオンを使用した場合の例:
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;
};
ユニオンはCで多態性を提供します。
ユニオンの素晴らしい使用法はメモリのアライメントです。これは、PCL(Point Cloud Library)のソースコードで見つけました。 APIの単一データ構造は、SSEをサポートするCPUとSSEをサポートしないCPUの2つのアーキテクチャをターゲットにできます。たとえば、PointXYZのデータ構造は
typedef union
{
float data[4];
struct
{
float x;
float y;
float z;
};
} PointXYZ;
3つのフロートには、SSEアライメント用の追加のフロートが埋め込まれます。だから
PointXYZ point;
ユーザーは、たとえばx座標にアクセスするために、point.data [0]またはpoint.x(SSEサポートに応じて)にアクセスできます。より類似したより良い使用法の詳細は、次のリンクにあります: PCL documentation PointT types
union
キーワード、まだC++ 03で使用中1、ほとんどがC時代の名残です。最も明白な問題は、PODでのみ機能することです1。
ただし、ユニオンの概念はまだ存在しており、実際、Boostライブラリにはユニオンのようなクラスがあります。
boost::variant<std::string, Foo, Bar>
union
(すべてではないにしても)の利点のほとんどがあり、以下を追加します。
実際には、それはunion
+ enum
の組み合わせと同等であることが実証されており、(boost::any
はdynamic_cast
、RTTIを使用するため。
1ユニオンはC++ 11( 無制限ユニオン )でアップグレードされ、デストラクタを持つオブジェクトを含めることができるようになりましたが、ユーザーは(現在アクティブなユニオンメンバーで)デストラクタを手動で呼び出す必要があります。バリアントを使用する方がずっと簡単です。
組合に関するウィキペディアの記事 から:
ユニオンの主な有用性は、スペースを節約することです。これは、多くの異なるタイプを同じスペースに格納する方法を提供するためです。ユニオンも粗雑な多型を提供します。ただし、型のチェックは行われないため、適切なフィールドが異なるコンテキストでアクセスされることを確認するのはプログラマ次第です。ユニオン変数の関連フィールドは、通常、他の変数の状態によって決定されます。おそらく、囲んでいる構造体です。
1つの一般的なCプログラミングのイディオムは、値の生の表現に依存するコードで行われるように、ユニオンを使用して、ユニオンの1つのフィールドに割り当てて別のフィールドから読み取ることにより、C++がreinterpret_castと呼ぶものを実行します。
N個の異なるタイプの構成(パラメーターを定義する変数のセットである)があるとします。構成タイプの列挙を使用することにより、構成タイプのIDを持つ構造を、すべての異なるタイプの構成の和集合とともに定義できます。
このように、構成を渡す場所はどこでもIDを使用して構成データを解釈する方法を決定できますが、構成が巨大な場合、スペースを浪費する可能性のあるタイプごとに並列構造を強制することはありません。
unionsの、すでに高められた重要性の1つの最近のブーストは、Cの最近のバージョンで導入された厳密なエイリアス規則によって与えられました。標準。
C標準に違反することなく、type-punningにunions doを使用できます。
このプログラムにはunspecified behaviorがあります(float
とunsigned int
の長さが同じであると仮定したため)。undefined behavior( ここを参照 )。
#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;
}
ユニオンを使用するための実用的な例を1つ追加します-数式計算機/インタープリターを実装するか、計算に何らかの種類を使用します(たとえば、実行時に変更可能コンピューティングの一部を使用します数式-方程式を数値的に解く-ちょうど例)。そのため、次のように異なるタイプ(整数、浮動小数点、さらには複素数)の数値/定数を定義することができます。
struct Number{
enum NumType{int32, float, double, complex}; NumType num_t;
union{int ival; float fval; double dval; ComplexNumber cmplx_val}
}
したがって、メモリを節約し、より重要なこと-おそらく極端な量(実行時に定義された数を多く使用する場合)の小さなオブジェクト(クラス継承/ポリモーフィズムによる実装と比較して)の動的割り当てを回避します。しかし、さらに興味深いことに、このタイプの構造体では、C++ポリモーフィズムのパワー(たとえば、ダブルディスパッチが好きな場合など)を使用できます。この構造体のフィールドとしてすべての数値型の親クラスに「ダミー」インターフェイスポインターを追加し、生の型の代わりに/に加えてthis instanceを指すか、古き良き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);
...
}
//e.t.c. 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)で型チェックの代わりにポリモーフィズムを使用できます-メモリ効率のよい実装(小さなオブジェクトの動的割り当てなし)-.
http://cplus.about.com/od/learningc/ss/lowlevel_9.htm から:
ユニオンの使用はほとんどありません。ほとんどのコンピューターでは、ポインターとintのサイズは通常同じです。これは、両方とも通常CPUのレジスターに収まるためです。そのため、intへのポインタの高速でダーティなキャストまたはその他の方法を実行する場合は、ユニオンを宣言します。
union intptr { int i; int * p; }; union intptr x; x.i = 1000; /* puts 90 at location 1000 */ *(x.p)=90;
ユニオンの別の用途は、異なるサイズのメッセージが送受信されるコマンドまたはメッセージプロトコルです。各メッセージタイプは異なる情報を保持しますが、それぞれに固定部分(おそらく構造体)と可変部分ビットがあります。これがあなたの実装方法です。
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; }ユニオンメッセージタイプ{
struct msgstring50 m50; struct msgstring80 m80; struct msgint10 i10; struct msgack ack; }実際には、共用体は同じサイズですが、無駄なスペースではなく、意味のあるデータのみを送信するのが理にかなっています。 msgackのサイズはわずか16バイトですが、msgstring80のサイズは92バイトです。したがって、messagetype変数が初期化されると、そのサイズフィールドは、そのタイプに応じて設定されます。これを他の関数で使用して、正しいバイト数を転送できます。
ユニオンは、プログラムにマシンに依存しない情報を埋め込むことなく、単一のストレージ領域で異なる種類のデータを操作する方法を提供します。Pascalのバリアントレコードに類似しています。
コンパイラシンボルテーブルマネージャーで見られるような例として、定数がint、float、または文字ポインターであると仮定します。特定の定数の値は適切なタイプの変数に格納する必要がありますが、値が同じ量のストレージを占有し、タイプに関係なく同じ場所に格納される場合、テーブル管理に最も便利です。これがユニオンの目的です-いくつかのタイプのいずれかを合法的に保持できる単一の変数。構文は構造に基づいています。
union u_tag {
int ival;
float fval;
char *sval;
} u;
変数uは、3つのタイプのうち最大のものを保持するのに十分な大きさです。特定のサイズは実装依存です。使用法が一貫している限り、これらのタイプのいずれかをuに割り当ててから式で使用できます。