Perhaps I wasn’t paying enough attention, but private inheritance never really made much sense to me. I always brushed it off as some obscure C++ feature that’s only used in a handful of projects across the world. However, turns out it can be pretty useful.
Technically, private inheritance can be described as anonymous composition. In simpler terms, private inheritance is a way to have a member variable that doesn’t have a name.
|
|
In the code above, we define a structure S
that has got a std::string
value somewhere inside. This sample also shows how we’d access this value.
Of course, we could have used a member variable. This would probably be a better choice here since it would let us get rid of this cumbersome syntax. The only possible disadvantage of this approach is that you have to come up with a name for this variable, and we all know how difficult that is.
Alright, that wasn’t that useful or interesting. Let’s take a look at an actually meaningful example:
|
|
This structure doesn’t contain any data, just prohibits copying by deleting the copy constructor and copy assignment operations. This class can be used in two ways:
|
|
These two classes are identical in a sense that their objects can’t be copied and contain two int
member variables.
Since no_copy
doesn’t contain any data you’d think it wouldn’t take up any space. This would mean that the objects of types A
and B
would be unaffected by this no_copy
thing and their memory layouts would be the same, right?
Nope, C++ has to guarantee that all member variables have distinct memory addresses. Because of that this move_only
field is going to take up some space, making the objects of class A
slightly larger than they need to be.
But what about B
? There’s something in C++ called empty base optimization. Since you’re reading this, you can probably read so go read the page I linked. Here’s a condensed version: empty base objects don’t take up any space. Oh, and read this page too.
This optimization can be used not just by our little no_copy
class (by the way, boost provides something called boost/core/noncopyable which might be better depending on your use case). This is what std::tuple
is based on, for example. Keep in mind, though, that this optimization can be pretty brittle.
Another interesting example of a class that can benefit from this optimization is std::unique_ptr
. In a lot of cases, the Deleter
class doesn’t contain any data. In these cases, by storing this deleter as a private base, the std::unique_ptr
object can, for all intents and purposes, be treated as if it was just a simple pointer. I’m talking about memory layout, of course.
This is particularly useful with C APIs, where you have dedicated functions that destroy the passed object and free the memory. Let’s take a look at how we could use a smart pointer to manage such “C” objects.
|
|
As you can guess, case2
and case3
both return 8 (when using a 64-bit build, of course). But how big will the pointer be in case1
? 40 bytes. That’s a lot of bytes for a single pointer, even a smart one. Perhaps using std::function
is unnecessarily grotesque, but even if you use a simple function pointer, the resulting size will be 16 bytes, which is definitely less but still not perfect.
Hopefully, this post gave you some idea why private inheritance is a thing. Let’s sum up a little: