Empty Base Optimization

「空のクラス」を定義します。

Sky classという意味ではありませんよ。Empty classという意味です。

メンバ変数を持たず継承もしていない、とにかくデータとしての要素の無いクラスです。

このEmptyClassのサイズはいくつでしょうか?

Emptyという位なのでゼロなのかと思いきや、sizeofで計るとゼロ以外の数値になります。

Wandboxで見てみましょう

sizeof(Empty) = 1ですね。

なぜか?その答えはある名曲に隠されています。

言葉はいつも奥の方から後ろに虚しさ連れて教えてくれた
けれどこんなにもからっぽになったのに僕は歩き出した

__ゆず「からっぽ」

からっぽに見えても、みんな何かを抱えて生きているのです。ゼロなんてありえないんですね。C++のクラスも同じなんですね。

納得感を得たところで、改めて言うと、C++の型は全くデータメンバが無い状態でも単独でインスタンスが作られる場合必ずサイズを持ちます。

C++のテクニカルな理由で、「空のクラス」でも、「独立したオブジェクト」のサイズは0にできないのです。

__Effective C++ 第3版

「テクニカルな理由」というのは、インスタンス化された際にオブジェクトのアドレスをとれるようにする為です。サイズ0ではアドレスが決まりませんからね。

このようにc++では単独でインスタンス化されるオブジェクトは例外なくサイズを持ちます。

例えばメンバ変数の一部として定義された場合もサイズを持ちます。

この場合でも&Hoge::eを決めなければいけないのでeはサイズを持ちます。結果として sizeof(Hoge) > sizeof(int) となります。

たった1byteとはいえ無駄なメモリを消費することになります。

コピー禁止をmix-inするboost::noncpyable等はまさにメンバを持たない空のクラスですね。コピー禁止するたびにデータサイズが増加したらたまったものではありません。

そんなわけで、規格では、空の型が別の型を継承していて、継承元の型が既にサイズを持っている場合や、逆に空の型の継承先がサイズを持っている場合はEmptyClass自体の追加のサイズは無くしても良いことになっています。

 

型を継承した時に、追加のサイズを持たせない最適化のことをEBO(Empty Base Optimization)と言います。

EBOの効かないコンパイラなんてほとんどありませんが、場合によっては型サイズの肥大化やアライメントのズレの原因になるので注意する必要があります。

EBOを意識した実装

コンパイラが空のクラスを最適化してくれることは理解しました。

プログラマもEBOが効くコードを意識して書きましょう。

先のboost::noncopyableも空のクラスでしたが、他にも空のクラスを定義するシチュエーションは数多くあります。

例えばファンクタなんかも、往々にしてoperator()だけ定義して、メンバ変数も特に無いというケースがあるかと思います。

スマートポインタのデリータやコンテナのアロケータを渡して実装を決めるテンプレートクラスもEBOを意識した設計にすることでメモリ効率が上がります。

以下が一例になります。オリジナルのコンテナクラスにテンプレートパラメータとしてアロケータを渡す実装で考えてみましょう。

まずはEBOを意識しない設計。

普通にAllocatorをメンバとして持っています。

続いてEBOを意識した設計。

Allocatorを継承した設計になっています。

 

サイズを計ってみましょう。Wandboxで確認

このようにちょっと意識することでメモリ効率の良いクラス設計ができるようになります。

 

まとめ

  1. C++では単独でインスタンス化されるオブジェクトは全て1以上のサイズを持つ
  2. サイズを持つ理由はアドレスを決定できるようにするため
  3. 他にサイズを持っている型と継承関係を持つことで空の型のサイズは最適化される
  4. この最適化をEBOという
  5. ちょっとEBOを意識したクラス設計にすると「解ってる感」が演出されて良い

 

Pocket