名前空間
変種
操作

空の基底の最適化

提供: cppreference.com
< cpp‎ | language
 
 
C++言語
一般的なトピック
フロー制御
条件付き実行文
繰り返し文 (ループ)
ジャンプ文
関数
関数宣言
ラムダ関数宣言
inline 指定子
例外指定 (C++20未満)
noexcept 指定子 (C++11)
例外
名前空間
指定子
decltype (C++11)
auto (C++11)
alignas (C++11)
記憶域期間指定子
初期化
代替表現
リテラル
ブーリアン - 整数 - 浮動小数点
文字 - 文字列 - nullptr (C++11)
ユーザ定義 (C++11)
ユーティリティ
属性 (C++11)
typedef 宣言
型エイリアス宣言 (C++11)
キャスト
暗黙の変換 - 明示的な変換
static_cast - dynamic_cast
const_cast - reinterpret_cast
メモリ確保
クラス
クラス固有の関数特性
特別なメンバ関数
テンプレート
その他
 
 

空の基底の部分オブジェクトのサイズをゼロにすることを可能とします。

[編集] 説明

あらゆるオブジェクトまたはメンバ部分オブジェクトのサイズは、たとえその型が空のクラス型 (つまり非静的データメンバを持たないクラスまたは構造体) であっても、 ([[no_unique_address]] でない限り -- 下記参照) (C++20以上) 同じ型の異なるオブジェクトのアドレスが必ず異なることを保証することが可能であるように、少なくとも1であることが要求されます。

しかし、基底クラス部分オブジェクトにはそのような制約がなく、オブジェクトのレイアウトから完全に削除する最適化が行えます。

#include <cassert>
 
struct Base {}; // 空のクラス。
 
struct Derived1 : Base {
    int i;
};
 
int main()
{
    // 空のクラス型のオブジェクトのサイズは少なくとも1です。
    assert(sizeof(Base) >= 1);
 
    // 空の基底の最適化が適用されます。
    assert(sizeof(Derived1) == sizeof(int));
}

空の基底クラスのいずれかが最初の非静的データメンバの型またはその基底でもある場合は、同じ型の2つの基底部分オブジェクトは最も派生した型のオブジェクト表現内で異なるアドレスを持つことが要求されるため、空の基底の最適化は禁止されます。

そのような状況の一般的な例は std::reverse_iterator のナイーブな実装です (空の基底 std::iterator から派生します)。 これはベースとなるイテレータ (これも std::iterator から派生します) をその最初の非静的データメンバとして保持します。

#include <cassert>
 
struct Base {}; // 空のクラス。
 
struct Derived1 : Base {
    int i;
};
 
struct Derived2 : Base {
    Base c; // Base (1バイトを占めます) の後に i のためのパディングがあります。
    int i;
};
 
struct Derived3 : Base {
    Derived1 c; // Base から派生。 sizeof(int) バイトを占めます。
    int i;
};
 
int main()
{
    // 空の基底の最適化は適用されません。
    // 基底が1バイトを占め、 Base メンバが1バイトを占め、
    // int のアライメント要件を満たすために2バイトのパディングが続きます。
    assert(sizeof(Derived2) == 2*sizeof(int));
 
    // 空の基底の最適化は適用されません。
    // 基底が少なくとも1バイトに加えて
    // 最初のメンバ (int と同じアライメント) のアライメント要件を
    // 満たすためのパディングを占めます。
    assert(sizeof(Derived3) == 3*sizeof(int));
}

reinterpret_cast を用いて変換された標準レイアウトオブジェクトへのポインタがその最初のメンバを指すという要件を維持するために、空の基底の最適化は StandardLayoutType に対しては必須です。 これは標準レイアウト型に対する要件のひとつが「非静的データメンバを持つ基底クラスを持たず、最初の非静的データメンバと同じ型の基底クラスを持たない」である理由です。

(C++11以上)

空のメンバ部分オブジェクトは、 [[no_unique_address]] 属性を使用すれば、空の基底と同様に最適化で削除することが許されます。 そのようなメンバのアドレスを取ると、同じオブジェクトの他の何らかのメンバのアドレスと等しいアドレスになることがあります。

#include <cassert>
 
struct Empty {}; // 空のクラス。
 
struct X {
    int i;
    [[no_unique_address]] Empty e;
};
 
int main()
{
    // 空のクラス型のオブジェクトのサイズは少なくとも1です。
    assert(sizeof(Empty) >= 1);
 
    // 空のメンバが最適化で削除されます。
    assert(sizeof(X) == sizeof(int));
}
(C++20以上)

[編集] ノート

空の基底の最適化は、アロケータがステートレスな場合にアロケータメンバのための追加の記憶域を占めることを避けるために、アロケータ対応の標準ライブラリクラス (std::vectorstd::functionstd::shared_ptr など) で一般的に使用されています。 これは要求されるデータメンバ (begin や end、 vector の場合は capacity ポインタなど) のいずれかをアロケータと共に boost::compressed_pair の同等品に格納することによって達成されます。


[編集] 参考文献

  • C++11 standard (ISO/IEC 14882:2011):
  • 5.10 Equality operators [expr.eq](p: 2)
  • 5.3.3 Sizeof [expr.sizeof](p: 2)
  • 9 Classes [class](p: 4,7)
  • 9.2 Class members [class.mem](p: 20)
  • C++98 standard (ISO/IEC 14882:1998):
  • 5.10 Equality operators [expr.eq](p: 2)
  • 5.3.3 Sizeof [expr.sizeof](p: 2)
  • 9 Classes [class](p: 3)