26

I was reading about Empty Base Optimization(EBO). While reading, the following questions popped up in my mind:

  1. What is the point of using Empty class as base class when it contributes nothing to the derived classes (neither functionality-wise, nor data-wise)?

  2. In this article, I read this:

//S is empty
class struct T : S
{
      int x;
};

[...]

Notice that we didn’t lose any data or code accuracy: when you create a standalone object of type S, the object’s size is still 1 (or more) as before; only when S is used as base class of another class does its memory footprint shrink to zero. To realize the impact of this saving, imagine a vector that contains 125,000 objects. The EBO alone saves half a megabyte of memory!

Does it mean that if we don't use "S" as base class of "T", we would necessarily consume double of megabyte of memory? I think, the article compares two different scenarios which I don't think is correct.

I would like to know a real scenario when EBO can proven to be useful.(means, in the same scenario, we would necessarily be at loss IF we don't use EBO!).

Please note that if your answer contains explanations like this :

The whole point is that an empty class has non-zero size, but when derived or deriving it can have zero size, then I'm NOT asking that, as I know that already. My question is, why would anyone derive his class from an empty class in the first place? Even if he doesn't derive and simply writes his class (without any empty base), is he at loss in ANY way?

Community
  • 1
  • 1
Nawaz
  • 353,942
  • 115
  • 666
  • 851
  • @Suma... what did you change in my post? :-/.. I'm unable to figure out.. – Nawaz Dec 01 '10 at 15:58
  • Why do you say the base class contributes nothing functionality-wise? – jalf Dec 01 '10 at 16:14
  • @jalf .... so far I didn't see any example.. by the way, by functionality I meant functions that do something, rather than nothing – Nawaz Dec 01 '10 at 16:17
  • @Nawaz: but why shouldn't the base class functions be able to do "do something"? – jalf Dec 01 '10 at 16:41
  • @jalf .. please give me some example.. I would be glad to know that :-) – Nawaz Dec 01 '10 at 16:44
  • `void foo() { printf("hello world"); }`? This function clearly does something, but it doesn't rely on any class data members. I was just wondering what made you think that functions in the base class couldn't "do something". Is it because they can't see any class data members? – jalf Dec 01 '10 at 18:02
  • @jalf ... What is the use of that function in the real world? Please note that, as I said in my question, I would like to know "real scenario" when EBO can proven to be useful – Nawaz Dec 01 '10 at 18:13
  • @Nawaz: as @icecrime's answer showed, it may be used to define policies allowing you to customize your classes through templates. Or it may be simply to make the same helper functions available to a large number of different derived classes. What I'm getting at is that I don't understand your implicit assumption that the functions in the base class can't "do anything". Why should they be different, or less capable, than all the other functions in your program? – jalf Dec 01 '10 at 19:15
  • it's commonly used with the standard library allocators, to allow you to customize the memory allocation policy for a container without increasing the size of the container (since C++03 allocators have to be stateless) – jalf Jan 07 '11 at 11:45
  • @jalf : Yes, I've seen that while exploring STL implemnetation by MSVC++. Just didn't realise those were empty classes. – Nawaz Jan 07 '11 at 12:09
  • What is `class struct T`? – L. F. Mar 23 '19 at 04:03
  • 1
    Empty class doesn't mean no "members", it means "no data members". It can still have "function members". So you may need that functionality, and the most simple way to reuse that functionality is by composition. However, if you suspect that object could have no data-members, you may prefer to inherit privately for such type to reduce the memory footprint of the derived class. It's not about "I would inherit from A because it's good for A", but, "I prefer to inherit instead of composing to benefit from EBO because I need the object A within B anyway". – ABu Apr 26 '20 at 11:51

9 Answers9

47

EBO is important in the context of policy based design, where you generally inherit privately from multiple policy classes. If we take the example of a thread safety policy, one could imagine the pseudo-code :

class MTSafePolicy
{
public:
  void lock() { mutex_.lock(); }
  void unlock() { mutex_.unlock(); }

private:
  Mutex mutex_;
};

class MTUnsafePolicy
{
public:
  void lock() { /* no-op */ }
  void unlock() { /* no-op */ }
};

Given a policy based-design class such as :

template<class ThreadSafetyPolicy>
class Test : ThreadSafetyPolicy
{
  /* ... */
};

Using the class with a MTUnsafePolicy simply add no size overhead the class Test : it's a perfect example of don't pay for what you don't use.

icecrime
  • 74,451
  • 13
  • 99
  • 111
  • Are you saying that in this example, classes `MTSafePolicy` and `MTUnsafePolicy` are analogous to interfaces in Java? In that case, why don't anyone mark it as abstract, in order to prevent instantiation. – Sourav Kannantha B Feb 08 '21 at 07:55
  • 1
    @SouravKannanthaB They aren't being derived from. Using abstract has no benefice. They share a common interface, but compile-time polymorphism shown here does not require that commonality to be defined, both classes just have to be compatible wherever they are used. With C++20 you can formalize these commonalities using Concepts if you want. – François Andrieux Aug 25 '22 at 18:20
7

EBO isn't really an optimization (at least not one that you do in the code). The whole point is that an empty class has non-zero size, but when derived or deriving it can have zero size.

This is the most usual result:

class A { };
class B { };

class C { };
class D : C { };

#include <iostream>
using namespace std;

int main()
{
        cout << "sizeof(A) + sizeof(B) == " << sizeof(A)+sizeof(B) << endl;
        cout << "sizeof(D) == " << sizeof(D) << endl;

        return 0;
}

Output:

sizeof(A) + sizeof(B) == 2
sizeof(D) == 1

To the edit: The optimization is, that if you actually do derive (for example from a functor, or from a class that has only static members), the size of your class (that is deriving) won't increase by 1 (or more likely 4 or 8 due to padding bytes).

Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151
  • @Let_Me_Be... I've edited my question.. .please see it again.. I hope you will now understand what I'm asking for. :-) – Nawaz Dec 01 '10 at 14:27
  • @Let_Me_Be...thanks for this update.. that makes sense now..i'm looking for more response...with more scenarios.. – Nawaz Dec 01 '10 at 14:38
  • @Nawaz What exactly do you mean by scenarios? – Šimon Tóth Dec 01 '10 at 14:39
  • @Nawaz So you want to know how you can have an empty class? Well, it can't contain any non-static attributes or virtual functions. The usage options are pretty much limitless. For example: friend injection, or generally most other clever uses of Curiously Recurring Pattern (like instance counters). – Šimon Tóth Dec 01 '10 at 14:46
  • @Let_Me_Be .. btw what is the advantage of deriving your classes from a class with "only" static members? I don't see any point :-/ – Nawaz Dec 01 '10 at 15:24
  • @Nawaz `struct A { static void method() {} }; struct B : A {}; B::method();` – Šimon Tóth Dec 01 '10 at 15:29
  • @Let_Me_Be...oh thought we cannot access static memthod like that..I never tried that myself.. thanks a lot.. +1 from me.. – Nawaz Dec 01 '10 at 15:48
  • "_EBO isn't really an optimization_" EBO is just a particular case of using **trailing padding** to store derived members, which is an optimisation of the layout of derived classes, compared to the naive representation where the derived class actually store its base class as a normal struct member – curiousguy Nov 26 '11 at 04:32
6

The "Optimization" in the EBO means the case when you use base class can be optimized to use less memory than if you use a member of the same type. I.e. you compare

struct T : S 
{
      int x;
};

with

struct T
{
      S s;
      int x;
};

not with

struct T
{
      int x;
};

If your question is why would you have an empty class at all (either as a member, or as a base), it is because you use its member functions. Empty means it has no data member, not that it does not have any members at all. Things like this are often done when programming with templates, where the base class is sometimes "empty" (no data members) and sometimes not.

Suma
  • 33,181
  • 16
  • 123
  • 191
5

Its used when programmers want to expose some data to client without increasing the client class size. The empty class can contain enums and typedefs or some defines which the client can use.The most judicious way to use such a class it it to,inherit such a class privately. This will hide the data from outside and wil not increase your class size.

Vikas Garg
  • 51
  • 1
  • 1
2

There can be empty classes which do not have any member variables, but member functions (static or non static) which can act as utility classes, lets call this EmptyClass. Now we can have a case where we want to create a class (let's call it SomeClass) which have a containment kind of relation with EmptyClass, but not 'is-a' relation. One way is to create a member object of type EmptyClass in SomeClass as follows:

class EmptyClass  
{
public:
    void someFun1();
    static int someUtilityFun2();
};
//sizeof(EmptyClass) = 1


class SomeClass
{
 private:
    EmptyClass e;
    int x;
};
//sizeof(SomeClass) = 8

Now due to some alignment requirements compilers may add padding to SomeClass and its size is now 8 bytes. The better solution is to have a SomeClass derive privately from EmptyClass and in this way SomeClass will have access to all member functions of EmptyClass and won't increase the extra size by padding.

class SomeClass : private EmptyClass
{
private:
    int x;
} 
//sizeof(SomeClass) = 4
1

Most of the time, an empty base class is either used polymorphically (which the article mentions), as "tag" classes, or as exception classes (although those are usually derived from std::exception, which is not empty). Sometimes there is a good reason to develop a class hierarchy which begins with an empty base class.

Boost.CompressedPair uses the EBO to shrink the size of objects in the event that one of the elements is empty.

rlbond
  • 65,341
  • 56
  • 178
  • 228
1

EASTL has a good explanation as to why they needed EBO, its also explained in-depth in the paper they link to/credit

Necrolis
  • 25,836
  • 3
  • 63
  • 101
  • 1
    ... the article is too big... I'll read it when I get enough time..then I'll comment on it...:-)... thanks for the link btw. – Nawaz Dec 01 '10 at 15:20
  • 1
    Some quote from the article would be handy to prevent link rot (and to make this a real answer, not only a link) – Suma Oct 24 '18 at 05:13
0

EBO is not something the programmer influences, and/or the programmer would be punished for if (s)he chose not to derive from an empty base class.

The compiler controls whether for:

class X : emptyBase { int X; };
class Y { int x };

you get sizeof(X) == sizeof(Y) or not. If you do, the compiler implements EBO, if not, it doesn't.

There never is any situation where sizeof(Y) > sizeof(X) would occur.

FrankH.
  • 17,675
  • 3
  • 44
  • 63
-1

The primary benefit I can think of is dynamic_cast. You can take a pointer to S and attempt to dynamic_cast it to anything that inherits from S- assuming that S offers a virtual function like a virtual destructor, which it pretty much must do as a base class. If you were, say, implementing a dynamically typed language, you may well wish or need for every type to derive from a base class purely for the purposes of type-erased storage, and type checking through dynamic_cast.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    .. if S has virtual functions.. then most likely it's not an empty class! – Nawaz Dec 01 '10 at 15:30
  • @Nawez: Why not? The base class S might very well offer no-op implementations, or even pure virtual. Look at icecrime's answer; arguably `lock` should have been virtual. – MSalters Dec 01 '10 at 15:40
  • 1
    @MSalters... if a class has virtual functions, that means, it's size cannot be zero (atleast for real life compilers)..and as for icecrime's answer, non virtual lock would work perfectly! – Nawaz Dec 01 '10 at 15:45
  • 1
    @Nawaz: No class can ever have `sizeof(Class)==0`, empty or not. But we're talking specifically over the size of an empty base class subobject. It doesn't need its own vtable, nor a vtable pointer. Assume the common layout of a vtable pointer at offset 0; that would cause the zero-sized base class subobject to share its vtable pointer with the derived class. No problem: those should be identical anyway, that's pretty much the point of virtual functions. – MSalters Dec 02 '10 at 12:36