if (ShouldDelayInput()) { std::functionvoid(FancyApplication &)> func = [=](FancyApplication &app) mutable { app.ProcessMouseMoveEvent(MouseEvent); }); FancyQueue.Push(func); return false; } // rest of the code...
Introducing our C++ Tech Blog
June 30, 2017
One of our studio values is mastery and our programmers like to pen an internal tech newsletter which covers coding practices and more advanced C++ knowledge. Our Senior Core Tech Programmer, Valentin Galea wants to share some of these articles with you here, including a dedicated Q&A session after each blog post!
So if you like all things code, let us shepherd you towards the first newsletter in the series:
Introducing the C++ Tech Blog
Hi, I’m Valentin Galea, Senior Core-Tech programmer here at Splash Damage.
Bellow you’ll find the first edition of our C++ newsletter, almost unabridged from when it was originally sent by email. Enjoy!
Advanced C++: A Tale Of std::function
Some time ago whilst working on one of our projects I needed a system that does a sort of “delayed function execution”. Essentially I wanted that when some function were called, instead of doing their logic, I would “record” their arguments and push them in a queue so that at a later point in time I would call them. The project uses Unreal Engine 4 compiled with the latest Visual Studio 2015, so I had all C++11/14 at my disposal!
Indeed in C++11 and beyond there is already support for doing something like this: std::function
http://en.cppreference.com/w/cpp/utility/functional/function. In a nutshell I was now able to do this:
Notice how I capture by-copy everything – that’s the key aspect of what I wanted: delegates with state.
Now I love std::function
as much as the next guy, it’s a very powerful mechanism, but it has two hidden performance limitation: one minor, other quite major. We shall discuss each of them.
How does it work?
std::function
does something quite magical – it can accept any function, functor or lambda that matches its template arguments definition. Anything with operator ()
std::function
will happily gobble it up.
In order to achieve this, it uses a technique called type erasure. That’s a fancy name for quite a simple idea: bury a type into some sort of opaque container and later retrieve it based on a known signature. The most straightforward implementation of this is the C way of throwing stuff into void *
.
Obviously a more sophisticated mechanism is used here: C++ polymorphism, and because std::function
‘s main feature is calling stuff through operator ()
we can piggyback on it to call into a virtual base class that’s actually derived to wrap our target object. You’ll see this in detail below. Also C++11 makes this easier by having variadic templates. In this way std::function
can accept any combination of arguments.
The downside is the cost of the virtual call but in the case of Unreal Engine it is amortized in the overall polymorphic chain of calls.
Where does stuff go?
Actually the biggest magic of std::function
is that it can accept any lambda no matter how “fat” it is (by that I mean it captured a lot of variables). How can it do that? Well easy: by calling malloc()
…
So herein lies the biggest problem of std::function
: it will heap allocate most of the time. It has to, because it’s so generic. I especially wanted to avoid this, as being a core-tech programmer I’m always mindful of performance.
In fairness, std::function
by standard has a small-object optimisation: if you feed it simple functions, it will just store the pointer to them inside itself.
A better solution
Like any programmer worth its salt I went ahead and reinvented the wheel and implemented my own std::function
!
The biggest change is that it doesn’t allocate any memory, instead it has a small fixed buffer and it works within the confines of that. If you try to push bigger stuff it will statically assert so the user has a chance to adjust.
Here is the source code:
NOTE: this is for illustrative purposes only, not a full production ready version – use with caution!
templatetypename Signature> struct Function; templatetypename Ret, typename... Args> struct FunctionRet(Args...)> { char Storage[128]; inline Function() = default; templatetypename Functor> inline Function(Functor && f) { static_assert(sizeof(f) = sizeof(Storage), "Max storage size hit, increase buffer!"); ::new(Storage) ImplFunctor>(f); } Function(const Function & other) = default; Function & operator =(const Function & other) = default; Function(Function &&) = default; Function & operator =(Function &&) = default; inline Ret operator ()(Args && ...args) { return GetImpl().Call(std::forwardArgs>(args)...); } struct Base { virtual ~Base() {} virtual Ret Call(Args &&...) = 0; }; templatetypename T> struct Impl : Base { inline Impl(const T & f) : Payload(f) {} inline Ret Call(Args && ...args) override final { return Payload(std::forwardArgs>(args)...); } T Payload; }; inline Base& GetImpl() { return *static_castBase *>(reinterpret_castvoid *>(Storage)); } };
C++17
The class template of std::function
allowed for some custom allocator support. With time, this proved unusable – the semantics were unclear, and there were technical issues with storing an allocator in a type-erased context and then recovering that allocator later during copy assignment for example.
The new C++17 standard removes all this.
The Unreal way
I mentioned this was a project using UE4. It maintains some game-specific alternatives to STL so it turns out there is already a TFunction
!
It seems quite well done and more importantly has some nice allocator optimisations: it will first use a small buffer like mine (up to 32 bytes) and after that cascade to a more general allocator. For more detail see Function.h in the UE4 public repository.
SG14
The C++ standardisation committee is organised in several subcommittees, one of which is SG14. It deals with embedded and video games programming – https://groups.google.com/a/isocpp.org/forum/#!forum/sg14. Several members are programmers from AAA games companies like ours.
Case in point: there is a discussion the about this very same topic I addressed here: they call it basic_function or inplace_function and if they succeed, sometime in the future it will be included in C++! See everything about it here: https://groups.google.com/a/isocpp.org/forum/#!topic/sg14/1Sw_qEdIYes
Closing notes
We managed to cover, at least in passing, some of the important aspects of std::function
and equivalent functional objects. I’m available for questions over at our forums, so head over there to pick my brain.
Until next time,
Valentin Galea