empty_bases

Microsoft 固有の仕様

C++ Standard では、最も派生したオブジェクトのサイズが 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");

1 つの非静的データ メンバーの型 char を持つクラスのサイズも 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 バイト、1 バイトの場合は 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 バイトのパディングが追加され、実質的に次の中Derived3Empty2余分なバイトが消費されます。

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でレイアウトされ、サブオブジェクトが重なるStruct1場合Empty1は発生しません。

既定のレイアウト アルゴリズムは、これらの制限に対処し、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 の対象になりません。 ただし、EBCO の対象となる基底クラスにDerived4適用される場合は、両方ともDerived4Derived5最適なレイアウトになります。

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
キーワード