Share via


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 バイトのパディングが追加され、その結果、実質的に次の中でEmpty2Derived3余分なバイトが消費されます。

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)
   | +---
   +---

最適なサイズですが Struct2Empty1 オフセット 1 内 Struct2 にレイアウトされますが、そのサイズ Struct2 を考慮して大きくされることはありません。 その結果、オブジェクトの配列AStruct2場合、サブオブジェクトのEmpty1A[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 固有の仕様はここまで

関連項目

__declspec
キーワード