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");
この結果は、空の基底クラスの最適化は、作業中の空の基底クラスの最適化 Derived1
、サイズは 2 バイトになります。 Empty1
の場合は 1 バイト、 Derived1::c
の場合は 1 バイトです。 クラス レイアウトは、空のクラスのチェーンがある場合にも最適です。
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
Derived3
サイズは 1 バイトですが、既定のクラス レイアウトではサイズが 2 バイトになります。 クラス レイアウト アルゴリズムでは、連続する 2 つの空の基底クラスの間に 1 バイトのパディングが追加されるため、実質的に、Derived3
内で余分なバイトEmpty2
消費されます。
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
を正しく配置するには、Empty3
後に 3 バイトの余分なパディングを追加する必要があります。
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
はStruct2
内のオフセット 1 に配置されますが、Struct2
のサイズは増やされません。 その結果、Struct2
オブジェクトの配列A
の場合、A[0]
のEmpty1
サブオブジェクトのアドレスは、A[1]
のアドレスと同じになりますが、これは当てはまるべきではありません。 この問題は、 Empty1
が Struct2
内のオフセット 0 にレイアウトされ、それによって 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
| +---
+---
__declspec(empty_bases)
はDerived5
に適用されますが、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 固有の仕様はここまで