はじめに
KMC 2回生のhatsusatoです。コミケまでもう1週間もないんですね1。27日まで授業があるとか正気の沙汰じゃない。
この記事は KMC アドベントカレンダー 2013 の 23 日目の記事です。昨日の記事は DtYaZsK 君の C++11超入門 でした。
今日は昨日に引き続いてC++11に関するお話です。
C++11で覚醒した共用体の話
共用体はたいていのC言語の入門書に載っている(と思う)ので、C言語をひと通り勉強した人なら共用体を知っていると思います。共用体はC言語の持つ低レベルなメモリ操作能力の一翼を担っています。しかし、共用体を実際に利用することは非常に稀でしょう。
実はこの共用体が、C++11での仕様変更によって生まれ変わりました。今回はそんな共用体にスポットライトを当ててみます。
共用体の基本
まず、C++11で共用体がどうなったのかを見る前に、これまでの共用体を復習しておきましょう。
C言語の共用体
C言語における共用体は、文法上はほとんど構造体と同じです。構造体の宣言の際には struct
キーワードが必要だったり、構造体のメンバにビット数を指定できたり2しますが、共用体も同様です。
構造体は各メンバの情報をメモリ上に順に並べて保持しますが、共用体は各メンバの情報をメモリ上の同じ位置に重なるようにして保持します。そのため、共用体は常にいずれか1つのメンバのみが有効な値を持ちます。ただし、共用体のメンバに複数の構造体があって、それらの構造体の先頭の方のメンバの型が一致するとき、それらの構造体のうちの1つが有効であるならば、それらの構造体のうちの他のメンバの型が一致している先頭の方のメンバも有効な値を持ちます3。この保証があるおかげで、共用体のどのメンバが有効なのかを、どのメンバが有効な時でも正しく表すタグを作ることができます。
C++の共用体
C++の共用体は基本的にはC言語の共用体をそのまま受け継いでいます。しかし、そもそもC++では struct
がほとんどクラスと同じ扱いになったため、共用体もほとんどクラスとして扱われます。つまり、アクセス制限を指定したりメンバ関数を持ったりできるという事です。一方で、もちろん共用体に固有の制限も存在します。共用体は以下の制限を満たします。
- 継承の機能を持たない
- ただし、「継承の機能を持たないクラス」とは以下を満たすものとする
- 仮想関数を持たない
- 基底クラスを持たない
- 基底クラスにならない
- 非自明なオブジェクト、またはその配列をメンバに持たない
- ただし、「非自明なクラス」とは以下のいずれか1つ以上を満たすものとする
- ユーザ定義コンストラクタを持つ
- ユーザ定義コピーコンストラクタを持つ
- ユーザ定義コピー代入演算子を持つ
- ユーザ定義デストラクタを持つ
static
データメンバを持たない- 参照を持たない
非自明なオブジェクトを持てないという制限によって、共用体はたいていのクラス型を保持することができませんが、メンバ関数を使ってカプセル化などを実現することはできます。
ただ、共用体をカプセル化したいのなら、上記のように共用体のメンバ関数を用いるより、無名共用体を使ったほうがタグ情報の重複を避けられてよりよいです。無名共用体はアクセス指定ができなかったりメンバ関数を持てなかったりしますが、クラススコープの中で作る限りは問題はないと思います。
C++11で覚醒した共用体
C++11になって、C++にはさまざまな機能が追加・更新されました。そのうちの1つが共用体の制限の緩和です。C++11での共用体は以下の制限を満たすクラスです。
- 継承の機能を持たない
- ただし、「継承の機能を持たないクラス」とは以下を満たすものとする
- 仮想関数を持たない
- 基底クラスを持たない
- 基底クラスにならない
static
でない参照を持たない
非自明なオブジェクトを持てなかったC++11以前の共用体と比べて、C++11の共用体は制限が大幅に緩和され、static
でない参照以外であれば何でも保持できるようになりました。これに伴って、共用体は暗黙に生成されるメンバ関数について以下の規則を満たします。
- 共用体の
static
でないメンバが自明な特殊メンバ関数を持たないとき、共用体の対応する特殊メンバ関数は暗黙に生成されない- ただし、「特殊メンバ関数」とは以下の関数のことである
- デフォルトコンストラクタ
- コピーコンストラクタ
- コピー代入演算子
- ムーブコンストラクタ
- ムーブ代入演算子
- デストラクタ
つまり、C++11以前では共用体に保持できなかったユーザ定義の特殊メンバ関数を持つオブジェクトは、共用体の方で特殊メンバ関数を適切にユーザ定義してやることで保持することができます。共用体内の非自明なオブジェクトを扱うときは、オブジェクトの構築には配置 new
を、解体には明示的なデストラクタ呼び出しをしてやる必要があります。
なお、C++11から入った非静的メンバのクラス内初期化は、共用体においては1つまで認められています。ただし、このクラス内初期化はメンバの初期化順序に注意しないと初期化に失敗する可能性があります。以下のサンプルコードが示すように、クラス内初期化される変数と、コンストラクタにより初期化されて有効になる変数が食い違い、かつクラス内初期化される変数がコンストラクタにより有効になる変数より後にあるとき、後にある変数の初期化によって前の変数の初期化が上書きされてしまいます4。そのため、共用体でのクラス内初期化は注意深く利用することをおすすめします。
C++11以前の共用体は不当に制限されていました。C++11の共用体は、およそ共用体が保持できると思われるものを何でも保持できます。共用体は覚醒したのです。
おわりに
C++11で共用体が柔軟で便利になったことを書いてきましたが、あなたが今まで共用体を使ったことがないのなら、これからも使うことはない可能性が高いでしょう。たいていの人は今回の記事をただ知識として覚えるか、忘れるかしてください。
明日の記事は dama 君の マインスイーパー雑感 です。
宣伝
KMCは今年の冬のコミックマーケットC85の 3日目31日(火)西地区“し”ブロック42b でゲームを収録したCDと部誌を頒布します。今回、私は「C++11 コア言語編」と題した記事を部誌に寄稿しました。今回の記事の共用体の話も含め、C++11のコア言語に追加された機能を概観しているので、よろしければぜひ 3日目西し42b までお越しください。詳しくはこちら。
参考文献
- 日本工業規格 JIS X 3010:2003 (プログラム言語C)
- 日本工業規格 JIS X 3014:2003 (プログラム言語C++)
- Working Draft, Standard for Programming Language C++ (Document Number: N3337)