Static, zero-overhead (probably) PIMPL in C++
There are a couple big caveats of PIMPL, and that's of course that you need to do dynamic memory allocation and suffer a level of pointer indirection, plus write a whole bunch of boilerplate! In this article I will propose something similar to PIMPL that does not require this sacrifice, and has (probably) no run time overhead compared to using standard private members.
PIMPL (Pointer to IMPLementation, or "opaque pointer") is an idiom used for when you need "super" encapsulation of members of a class - you don't have to declare privates, or suffer all of the #include bloat or forward declaration boilerplate entailed, in the class definition. It can also save you some recompilations, and it's useful for dynamic linkage as it doesn't impose a hidden ABI on the client, only the one that is also part of the API. Typical exhibitionist class: // Foo.hpp #include <big_header.hpp> class Foo { public: Foo(int); private: // how embarrassing! Dongle dongle; }; // Foo.cpp Foo(int bar) : dongle(bar) {} Now, it's developed a bad case of PIMPLs and decides to cover up: // Foo_PIMPL.hpp class Foo { public: // API stays the same... Foo(int); // with some unfortunate additions... ~Foo(); Foo(Foo const&); Foo &operator =(Foo const&); private: // but privates are nicely tucked away! struct Impl; Impl *impl; }; // Foo_PIMPL.cpp #include <big_header.hpp> struct Foo::Impl { Dongle dongle; }; Foo(int bar) { impl = new Impl{Dongle{bar}}; // hmm... } ~Foo() { delete impl; // hmm... } Foo(Foo const&other) { // oh no } Foo &operator =(Foo const&other) { // I hate everything } There are a couple big caveats of PIMPL, and that's of course that you need to do dynamic memory allocation and suffer a level of pointer indirection, plus write a whole bunch of boilerplate! In this article I will propose something similar to PIMPL that does not require this sacrifice, and has (probably) no run time overhead compared to using standard private members. [subheading]Pop that PIMPL![/subheading] So, what can we do about it? Let's start by understanding why we need to put private fields in the header in the first place. In C++, every class can be a value type, i.e. allocated on the stack. In order to do this, we need to know its size, so that we can shift the stack pointer by the right amount. Every allocation, not just on the stack, also needs to be aware of possible alignment restrictions. Using an opaque pointer with dynamic allocation solves this problem, because the size and alignment needs of a pointer are well-defined, and only the implementation has to know about the size and alignment needs of the encapsulated fields. It just so happens that C++ already has a very useful feature to help us out: std::aligned_storage in the #include <type_traits> template<class Impl, size_t Size, size_t Align> struct Pimpl { typename std::aligned_storage<Size, Align>::type mem; }; For convenience, we'll override the dereference operators to make it look almost like we're directly using the Impl structure. Impl &operator *() { return reinterpret_cast<Impl &>(mem); } Impl *operator ->() { return reinterpret_cast<Impl *>(&mem); } // be sure to add const versions as well! The last piece of the puzzle is to ensure that the user of the class actually provides a valid size and alignment, which ends up being quite trivial: Pimpl() { static_assert(sizeof(Impl) <= Size, "Impl too big!"); static_assert(Align % alignof(Impl) == 0, "Impl misaligned!"); } You could also add a variadic template constructor that forwards its parameters to the Impl constructor and constructs it in-place, but I'll leave that as an exercise to the reader. To end off, let's convert our Foo example to our new and improved PIMPL! // Foo_NewPIMPL.hpp class Foo { public: // API stays the same... Foo(int); // no boilerplate! private: struct Impl; // let's assume a Dongle will always be smaller than 16 bytes and require 4-byte alignment Pimpl<Impl, 16, 4> impl; }; // Foo_NewPIMPL.cpp #include <big_header.hpp> struct Foo::Impl { Dongle dongle; }; Foo(int bar) { impl->dongle = Dongle{bar}; } [subheading]Conclusion[/subheading] There's not much to say about it, really. Aside from the reinterpret_casts, there's no reason there could be any difference at run time, and even then the only potential difference would be in the compiler's ability to optimize. As always, I appreciate comments and feedback!
Related Tutorials
Balancing Game Development and Creative Direction in Indie Production
A practical look at how indie developers can balance creative direction with hands-on game development. This article co…
My Unreal Engine Development Process: From Core Idea to Playable Build
A practical overview of my Unreal Engine development process, covering how I move from a core game idea to a playable b…
Introducing LaneGraph: The Ultimate Road Network Solution for Unity
Discover the power of LaneGraph, a lightweight and flexible lane-based navigation system for Unity. LaneGraph makes it…
Retargeting Mixamo Characters with Root Motion In Unreal Engine 5.4.
I have always found Retargeting Mixamo Characters To have Root Motion is a serious lengthy Tast, Recently I stumbled up…
How To Make A SIMPLE Main Menu In Unity
In this tutorial for unity, i go over how to make a simple main menu for unity, it's an unlisted video because i do not…
Guide to Gameplay Balance
A perspective on competitive gameplay balance, from a background of "shooter" sandbox design.
Discussion
More from Boreal
Brain Dead Simple Game States
Lately, I've realized that game state management is always vastly overcomplicated. Here's a brain dead simple system t…
Implementing Component-Entity-Systems
This implementation works very well in the small scope of my program and can easily be extended to use more components …
Understanding Component-Entity-Systems
The traditional way to implement game entities was to use object-oriented programming. This led to large, rigid class h…
Discussion