empty_bases
Microsoft 固有の仕様
C++ 標準では、最も派生したオブジェクトのサイズが 0 以外である必要があり、1 つ以上のバイトのストレージを占有する必要があります。 要件は最も派生したオブジェクトにのみ適用されるため、基底クラスのサブオブジェクトはこの制約の対象になりません。 空の基底クラスの最適化 (EBCO) は、この自由を利用します。 その結果、メモリ消費量が減少し、パフォーマンスが向上する可能性があります。 Microsoft Visual C++ コンパイラでは、これまで EBCO のサポートが制限されていました。 Visual Studio 2015 Update 3 以降のバージョンでは、この最適化を最大限に活用するクラス型の新しい __declspec(empty_bases)
属性が追加されました。
重要
__declspec(empty_bases)
これを使用すると、ABI が適用される構造とクラス レイアウトが破壊的変更を引き起こす可能性があります。 このストレージ クラス属性を使用するときは、すべてのクライアント コードで、コードと同じ定義が構造体とクラスに使用されていることを確認します。
構文
__declspec( empty_bases )
解説
Visual Studio では、何 __declspec(align())
も指定されていない場合 alignas()
、空のクラスのサイズは 1 バイトです。
struct Empty1 {};
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");
型 char
の非静的データ メンバーが 1 つのクラスも、サイズが 1 バイトです。
struct Struct1
{
char c;
};
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");
クラス階層でこれらのクラスを組み合わせると、サイズが 1 バイトのクラスになります。
struct Derived1 : Empty1
{
char c;
};
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");
この結果は、空の基底クラスの最適化です。サイズが 2 バイトの場合 Derived1
と同じように、1 バイト、 Empty1
1 バイト Derived1::c
です。 クラス レイアウトは、空のクラスのチェーンがある場合にも最適です。
struct Empty2 : Empty1 {};
struct Derived2 : Empty2
{
char c;
};
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");
ただし、Visual Studio の既定のクラス レイアウトでは、複数の継承シナリオでは EBCO は利用されません。
struct Empty3 {};
struct Derived3 : Empty2, Empty3
{
char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error
サイズは 1 バイトでもかまいませんが Derived3
、既定のクラス レイアウトではサイズが 2 バイトになります。 クラス レイアウト アルゴリズムでは、連続する 2 つの空の基底クラスの間に 1 バイトのパディングが追加され、その結果、実質的に次の中でEmpty2
Derived3
余分なバイトが消費されます。
class Derived3 size(2):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
1 | c
+---
この最適でないレイアウトの効果は、後の基底クラスまたはメンバーサブオブジェクトの配置要件によって余分なパディングが強制される場合に複合されます。
struct Derived4 : Empty2, Empty3
{
int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error
型int
のオブジェクトの自然な配置は 4 バイトであるため、正しく配置Derived4::i
するには、後に 3 バイトの余分なパディングを追加Empty3
する必要があります。
class Derived4 size(8):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
| <alignment member> (size=3)
4 | i
+---
既定のクラス レイアウトのもう 1 つの問題は、空の基底クラスがクラスの末尾を越えたオフセットに配置される可能性があるということです。
struct Struct2 : Struct1, Empty1
{
};
static_assert(sizeof(Struct2) == 1, "Struct2 should be 1 byte");
class Struct2 size(1):
+---
0 | +--- (base class Struct1)
0 | | c
| +---
1 | +--- (base class Empty1)
| +---
+---
最適なサイズですが Struct2
、 Empty1
オフセット 1 内 Struct2
にレイアウトされますが、そのサイズ Struct2
を考慮して大きくされることはありません。 その結果、オブジェクトの配列A
のStruct2
場合、サブオブジェクトのEmpty1
A[0]
アドレスは、そのアドレスA[1]
と同じになりますが、これは当てはまるべきではありません。 この問題は、オフセット 0 内Struct2
にレイアウトされている場合Empty1
は発生せず、サブオブジェクトがStruct1
重なっていました。
既定のレイアウト アルゴリズムは、これらの制限に対処し、EBCO を完全に活用するように変更されていません。 このような変更はバイナリの互換性を損ないます。 EBCO の結果としてクラスの既定のレイアウトが変更された場合は、クラス定義を含むすべてのオブジェクト ファイルとライブラリを再コンパイルして、すべてのオブジェクト がクラス レイアウトに一致するようにする必要があります。 この要件は、外部ソースから取得したライブラリにも拡張されます。 このようなライブラリの開発者は、異なるバージョンのコンパイラを使用する顧客をサポートするために、EBCO レイアウトの有無にかかわらずコンパイルされた独立したバージョンを提供する必要があります。 既定のレイアウトを変更することはできませんが、クラス属性を追加してクラスごとにレイアウトを変更する手段を __declspec(empty_bases)
提供できます。 この属性で定義されたクラスは、EBCO を最大限に活用できます。
struct __declspec(empty_bases) Derived3 : Empty2, Empty3
{
char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // No Error
class Derived3 size(1):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
0 | +--- (base class Empty3)
| +---
0 | c
+---
すべてのサブオブジェクト Derived3
はオフセット 0 にレイアウトされ、そのサイズは最適な 1 バイトです。 覚えておくべき重要な点の 1 つは、 __declspec(empty_bases)
それが適用されるクラスのレイアウトにのみ影響を与えるということです。 基底クラスには再帰的に適用されません。
struct __declspec(empty_bases) Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // Error
class Derived5 size(8):
+---
0 | +--- (base class Derived4)
0 | | +--- (base class Empty2)
0 | | | +--- (base class Empty1)
| | | +---
| | +---
1 | | +--- (base class Empty3)
| | +---
| | <alignment member> (size=3)
4 | | i
| +---
+---
適用Derived5
されますが__declspec(empty_bases)
、直接空の基底クラスがないため、EBCO の対象にはなりません。そのため、効果はありません。 ただし、EBCO の対象となる基底クラスに Derived4
適用される場合は、両方 Derived4
とも Derived5
最適なレイアウトになります。
struct __declspec(empty_bases) Derived4 : Empty2, Empty3
{
int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // No Error
struct Derived5 : Derived4
{
};
static_assert(sizeof(Derived5) == 4, "Derived5 should be 4 bytes"); // No Error
class Derived5 size(4):
+---
0 | +--- (base class Derived4)
0 | | +--- (base class Empty2)
0 | | | +--- (base class Empty1)
| | | +---
| | +---
0 | | +--- (base class Empty3)
| | +---
0 | | i
| +---
+---
すべてのオブジェクト ファイルとライブラリがクラス レイアウトに同意する必要があるため、 __declspec(empty_bases)
自分が制御するクラスにのみ適用できます。 標準ライブラリのクラスや、EBCO レイアウトで再コンパイルされないライブラリに含まれるクラスには適用できません。
Microsoft 固有の仕様はここまで
関連項目
フィードバック
https://aka.ms/ContentUserFeedback」を参照してください。
以下は間もなく提供いたします。2024 年を通じて、コンテンツのフィードバック メカニズムとして GitHub の issue を段階的に廃止し、新しいフィードバック システムに置き換えます。 詳細については、「フィードバックの送信と表示