The importance of std::function

by Malte Skarupke

In C++11 there is a new class in the standard library called std::function. It allows you to store anything that is callable. For example

#include <functional>
#include <iostream>
void print_world()
{
    std::cout << "World!" << std::endl;
}

int main()
{
    std::function<void ()> hello_world = []{ std::cout << "Hello "; };
    hello_world();
    hello_world = &print_world;
    hello_world();
}

Prints “Hello World!” as you would expect. The assignment to and storage of the two different types is handled internally by the std::function.

The amazing thing is that calling a std::function is fairly cheap: It’s one virtual function call. That has wide implications for how you will use (or not use) virtual functions in the future. In many cases where you used to have to use a virtual function it is now better to use a std::function. For example for an update loop.

Classically an update loop would be implemented something like this:

struct Updateable
{
    virtual void update(float dt) = 0;
    virtual ~Updateable();
};

struct Mover : Updateable
{
    virtual void update(float dt) override;
};
struct Jumper : Updateable
{
    virtual void update(float dt) override;
};

int main()
{
    std::vector<std::unique_ptr<Updateable>> updateables;
    updateables.emplace_back(new Mover);
    updateables.emplace_back(new Jumper);
    while (true)
    {
        for (auto & updateable : updateables)
        {
            updateable->update(0.016f);
        }
    }
}

And this is a good solution at first. But you’ve got that inheritance in there, and that doesn’t scale. You will probably want to add a render loop and maybe a separate update loop for the editor. Once you’ve got that many base classes it makes sense to combine them into one. And now you’ve just started on the way of having a big base class that gets used everywhere. Soon enough adding something different to the update loop involves a whole lot of unnecessary work.

Here is an alternate update loop using std::function:

struct Wanderer
{
    explicit Wanderer(std::vector<std::function<void (float)>> & update_loop)
    {
        update_loop.emplace_back([this](float dt) { update(dt); });
    }
    void update(float dt);
};

struct Diver
{
    explicit Diver(std::vector<std::function<void (float)>> & update_loop)
    {
        update_loop.emplace_back([this](float dt) { update(dt); });
    }
    void update(float dt);
};

int main()
{
    std::vector<std::function<void (float)>> update_loop;
    Wanderer wanderer{update_loop};
    Diver diver{update_loop};

    while (true)
    {
        for (auto & function : update_loop)
        {
            function(0.016f);
        }
    }
}

This has no inheritance. Meaning I can add anything to the update loop now. If I create a new object that is unrelated to anything I had before and is managed completely differently, I can still add it just as easily as anything else. And I can use the same idea for a render loop or for implementing an observer pattern. It makes everything immensely easier.

So how does this perform?

edit: My initial measurements were incorrect for the debug version. I have new measurements in this post. Spoilers: std::function is faster than a virtual function in optimized code, and slower than a virtual function in debug code.

There are downsides to std::functions: They use more space, they may have to perform a heap allocation on construction (depending on the size of your functor) and they may add to your compile and link time because you’re compiling a new template for everything. I don’t consider these to be big problems. The heap allocation disqualifies it from a few use cases, but you probably don’t care in most situations. For the build time problem it is probably enough if you are aware of it and keep std::function construction and assignment out of your headers.

The speed of std::function has big implications. It means that you can get rid of a lot of virtual functions and a whole lot of inheritance. Look at every single instance where you are using virtual functions or function pointers and think about if you can not use a std::function instead. You will find that fairly often you can, and you should. It’s going to take some time to figure out the new rules for this, but for now my rules are this: If I have a base class which has a derived class that has different behavior: Use a virtual function. If I have a function that has several different implementations: Use a std::function. You can also combine the two and use both (which is then a bit slower than using a virtual function only).

Next time: How do you remove objects from this update loop?