description | title | ms.date | f1_keywords | helpviewer_keywords | ||||
---|---|---|---|---|---|---|---|---|
Learn more about: empty_bases |
empty_bases |
03/01/2022 |
|
|
Microsoft Specific
The C++ Standard requires that a most-derived object must have a non-zero size and must occupy one or more bytes of storage. Because the requirement only extends to most-derived objects, base class subobjects aren't subject to this constraint. The Empty Base Class Optimization (EBCO) takes advantage of this liberty. It results in reduced memory consumption, which can improve performance. The Microsoft Visual C++ compiler has historically had limited support for EBCO. In Visual Studio 2015 Update 3 and later versions, we've added a new __declspec(empty_bases)
attribute for class types that takes full advantage of this optimization.
Important
Use of __declspec(empty_bases)
can cause an ABI-breaking change in structure and class layout where it's applied. Make sure that all client code uses the same definitions for structures and classes as your code when you make use of this storage class attribute.
__declspec( empty_bases )
In Visual Studio, absent any __declspec(align())
or alignas()
specifications, an empty class is 1 byte in size:
struct Empty1 {};
static_assert(sizeof(Empty1) == 1, "Empty1 should be 1 byte");
A class with a single non-static data member of type char
is also 1 byte in size:
struct Struct1
{
char c;
};
static_assert(sizeof(Struct1) == 1, "Struct1 should be 1 byte");
Combining these classes in a class hierarchy also results in a class that's 1 byte in size:
struct Derived1 : Empty1
{
char c;
};
static_assert(sizeof(Derived1) == 1, "Derived1 should be 1 byte");
This result is the Empty Base Class Optimization at work, as without it Derived1
would be 2 bytes in size: 1 byte for Empty1
and 1 byte for Derived1::c
. The class layout is also optimal when there's a chain of empty classes:
struct Empty2 : Empty1 {};
struct Derived2 : Empty2
{
char c;
};
static_assert(sizeof(Derived2) == 1, "Derived2 should be 1 byte");
However, the default class layout in Visual Studio doesn't take advantage of EBCO in multiple inheritance scenarios:
struct Empty3 {};
struct Derived3 : Empty2, Empty3
{
char c;
};
static_assert(sizeof(Derived3) == 1, "Derived3 should be 1 byte"); // Error
Although Derived3
could be 1 byte in size, the default class layout results in it being 2 bytes in size. The class layout algorithm is adding 1 byte of padding between any two consecutive empty base classes, effectively resulting in Empty2
consuming an extra byte within Derived3
:
class Derived3 size(2):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
1 | c
+---
The effects of this suboptimal layout are compounded when the alignment requirements of a later base class or member subobject force extra padding:
struct Derived4 : Empty2, Empty3
{
int i;
};
static_assert(sizeof(Derived4) == 4, "Derived4 should be 4 bytes"); // Error
The natural alignment for an object of type int
is 4 bytes, so 3 bytes of extra padding must be added after Empty3
to correctly align Derived4::i
:
class Derived4 size(8):
+---
0 | +--- (base class Empty2)
0 | | +--- (base class Empty1)
| | +---
| +---
1 | +--- (base class Empty3)
| +---
| <alignment member> (size=3)
4 | i
+---
Another issue with the default class layout is that an empty base class may be laid out at an offset past the end of the class:
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)
| +---
+---
Although Struct2
is the optimal size, Empty1
is laid out at offset 1 within Struct2
but the size of Struct2
isn't increased to account for it. As a result, for an array A
of Struct2
objects, the address of the Empty1
subobject of A[0]
will be the same as the address of A[1]
, which shouldn't be the case. This issue wouldn't occur if Empty1
were laid out at offset 0 within Struct2
, thereby overlapping the Struct1
subobject.
The default layout algorithm hasn't been modified to address these limitations and fully take advantage of EBCO. Such a change would break binary compatibility. If the default layout for a class changed as a result of EBCO, every object file and library that contains the class definition would need to be recompiled so they all agree on the class layout. This requirement would also extend to libraries obtained from external sources. The developers of such libraries would have to provide independent versions compiled both with and without the EBCO layout to support customers who use different versions of the compiler. Although we can't change the default layout, we can provide a means to change the layout on a per-class basis with the addition of the __declspec(empty_bases)
class attribute. A class defined with this attribute can make full use of 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
+---
All of the subobjects of Derived3
are laid out at offset 0, and its size is the optimal 1 byte. One important point to remember is that __declspec(empty_bases)
only affects the layout of the class to which it's applied. It isn't applied recursively to base classes:
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
| +---
+---
Although __declspec(empty_bases)
is applied to Derived5
, it isn't eligible for EBCO because it doesn't have any direct empty base classes, so it has no effect. However, if instead it's applied to the Derived4
base class, which is eligible for EBCO, both Derived4
and Derived5
will have optimal layout:
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
| +---
+---
Because of the requirement that all object files and libraries agree on the class layout, __declspec(empty_bases)
can only be applied to classes that you control. It can't be applied to classes in the standard library, or to classes included in libraries that aren't also recompiled with the EBCO layout.
END Microsoft Specific