Вопрос

Most C++ programmers know about the empty base class optimazation as a technique/idiom. What happens with empty child classes? For example

class EmptyBase {
    int i;
};

template<typename T>
class Derived : T { 
};

std::cout << sizeof(Derived<EmptyBase>); // Is there a standard verdic on this?

Similarly to the EBO there should be an EDO stating that since a derived class doesn't provide any more members, nor introduces any virtual ones to its parametrizing type, it should not require more memory. Considering the various situations in which something like that might apper (multiple inheritance, single inheritance ...) :

  • Is such an optimization standard/possible?
  • If yes, what are the mechanics of such an optimization, are they similar to EBO's?

Note: Using class templates that derive from their parameterizing types is fairly typical. The topic is about space wasting in such situations

Это было полезно?

Решение

The standard does not contain an "empty base class" case per se. Rather, it says (cf. 1.8):

[A] most derived object shall have a non-zero size and shall occupy one or more bytes of storage. Base class subobjects may have zero size.

And:

Unless an object is [...] a base class subobject of zero size, the address of that object is the address of the first byte it occupies. Two objects [...] may have the same address if one is a subobject of the other, or if at least one is a base class subobject of zero size and they are of different types; otherwise, they shall have distinct addresses.

And (Clause 9):

Complete objects and member subobjects of class type shall have nonzero size. Footnote: Base class subobjects are not so constrained.

This never says that only empty bases are amenable to any kind of layout change and leaves ample room for "squeezing" the layout: for example:

struct A {}; struct B : A { int x; };             // "equivalent" to { int }

struct X { int a; }; struct Y : X {};             // "equivalent" to { int }

Consider B b; and Y y;. it is possible that the address of b, that of the A-subobject of b (i.e. &static_cast<A&>(b)) and that of b.x are the same, and similarly that the address of y, the X-suboject of y, and the address of y.a are the same.

The only thing that does not work is this:

struct S {}; struct T { S s; };                   // "equivalent" to { char }

By "equivalent" I mean the most sensible, space-saving implementation.

A more interesting case is the following:

struct Foo { int x; char a; };                    // { int, char, char[3] }

struct Bar : Foo { short q; };                    // { int, char, char, short }

This example assumes sizeof(int) == 4 and sizeof(short) == 2. We have sizeof(Foo) == 8 for alignment reasons, but sizeof(Bar) is also 8 despite having more data members.


Another relevant part of the standard is 9.2/13:

Nonstatic data members of a (non-union) class with the same access control (Clause 11) are allocated so that later members have higher addresses within a class object. The order of allocation of non-static data members with different access control is unspecified (11). Implementation alignment requirements might cause two adjacent members not to be allocated immediately after each other; so might requirements for space for managing virtual functions (10.3) and virtual base classes (10.1).

Finally, 9.2/10 says that standard-layout classes have no padding at the beginning, so that their address equals the address of their "initial member". Since standard-layout requires that all bases either be empty, or that the most-derived class itself have no data members, this means that standard-layout classes must employ a kind of "empty base" optimization, and the initial part of the layout of my B and Y above is actually mandatory.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top