Probably Dance

I can program and like games

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?

Share this:

  • Twitter
  • Facebook
Published: December 16, 2012
Filed Under: Programming
Tags: C++ : c++11

7 Comments to “The importance of std::function”

  1. spacer Marton says:
    January 31, 2013 at 08:18

    Very nice idea! It does make a lot of things easier. So, tell me the answer: How do you remove objects from this update loop?

    Reply
  2. spacer Malte Skarupke says:
    February 2, 2013 at 16:04

    That one turned out to be complicated. I ended up at the simplest possible design where I assign each added function an ID and you can remove it using that ID.
    The complicated part was making that convenient. My idea is to return a struct that removes the object in it’s destructor. So if you add a class member function to the update loop you just store the returned struct inside the class and whenever it gets destroyed the function automatically gets removed.
    And I was going to write a bit about that and about a very clean signal/slots implementation where everything is nicely decoupled and I allow you to sort by type, so that you can say “I need to update after that type has updated.”

    Except then you’re running into problems because objects tend to remove themselves or other objects from the update loop in their update function. Or they add new objects. Which you don’t want to happen while you’re iterating through the loop. And with that struct those objects don’t have control over when the removal happens.
    So you’d need some delayed remove code but that’s not easy if you’re dealing with all kinds of different objects. So I had to move the delayed remove code into the signal itself because it was the only place where you know what’s going on. And it turns out that that has it’s own problems because it makes the container more complicated.

    Basically it’s no longer a nice and clean implementation, so it’s not as much fun to write about that.

    But if you just do the “here’s an ID, you can remove yourself again if you give me the ID” approach, you should get pretty far, even if it is a bit annoying to the user.

    Reply
    • spacer Guillaume Papin says:
      March 6, 2013 at 06:02

      What about using a std::list instead of std::vector, and take advantages of the list iterators as an “ID”.

      #include &lt;list&gt;
      #include &lt;functional&gt;
      
      using update_list    = std::list&lt;std::function&lt;void (float)&gt;&gt;;
      using update_list_id = update_list::iterator;
      
      struct Wanderer
      {
        update_list    update_loop_;
        update_list_id update_id_;
      
        explicit Wanderer(std::list&lt;std::function&lt;void (float)&gt;&gt; &amp; update_loop)
          : update_loop_(update_loop)
        {
          update_id_ = update_loop.insert(update_loop.end(),
                                         [this](float dt) { update(dt); });
        }
        void update(float dt);
      
        ~Wanderer()
        {
          // efficient deletion directly by iterator
          update_loop_.erase(update_id_);
        }
      };
      
      struct Diver
      {
        update_list    update_loop_;
        update_list_id update_id_;
      
        explicit Diver(std::list&lt;std::function&lt;void (float)&gt;&gt; &amp; update_loop)
          : update_loop_(update_loop)
        {
          update_loop.emplace_back([this](float dt) { update(dt); });
          update_id_ = --update_loop.end(); // far less cryptic to use insert() I guess...
        }
        void update(float dt);
      
        ~Diver()
        {
          update_loop_.erase(update_id_);
        }
      
      };
      
      int main()
      {
        update_list update_loop;
        Wanderer    wanderer{update_loop};
        Diver       diver{update_loop};
      
        while (true)
          {
            for (auto &amp; function : update_loop)
              {
                function(0.016f);
              }
          }
      }
      

      It doesn’t solve a everything, more work is needed to manage the deletion at the right time but it’s quite straightforward.

      Reply
      • spacer Malte Skarupke says:
        March 7, 2013 at 21:58

        Yes, that would make erasing very easy. The downside would be that everything gets it’s own allocation on the heap. Because of that iterating over a list is much slower than iterating over a vector. And I don’t think that this is a place where it would be a good trade-off to make erasing easier if that makes iterating over the list slower.

  3. spacer Stefan Boberg says:
    March 5, 2013 at 12:22

    What’s not so good about it: hidden memory allocations and massive linker bloat.

    I wish C++ actually had proper first-class delegates, so that we would have to rely on some overly complex template machinery.

    Reply
    • spacer Malte Skarupke says:
      March 5, 2013 at 20:28

      For the linker bloat I am still hopeful that it won’t be too bad, because std::function is not a bad template. You can implement it without too much template magic and sometimes templates actually compile pretty quickly. I haven’t used std::function in a big project, so I’d be interested if you have experience for example with boost::function.

      My expectation at the moment is that memory allocations won’t be a big problem because most functor objects are small, and I don’t expect that to change. And hopefully everyone will implement a small functor optimization soon.

      First-class delegates would be cool, but I wouldn’t know how to implement them without template machinery. In C++ you have to remain type safe and call copy/move constructors and destructors properly. Other languages that have first-class delegates just do pointer assignment and then rely on the garbage collector for cleaning up. That’s actually one of the things that annoyed me about the D programming language: You can’t use delegates without the garbage collector.
      So even if we had first class delegates, you would still have to generate all of that code. You might be able to do that faster if you didn’t have to go through templates, but I’m not convinced that it would be a big gain.

      Reply
  4. spacer MihailNaydenov says:
    May 16, 2014 at 05:32

    I personally find myself using std::function all over the place, to the extend I feel guilty about it!
    Basically almost every class, except for the very basic ones, have few of these.

    This way I can delegate part of the class functionality, which is not strictly part of the class purpose, to others to “implement” (or not).

    And I also noticed, I can use std::function in the place of inheritance, not only that, but in many ways it can be more powerful, because different part of the “interface” can be “implemented” by different, completely unrelated objects. And you can even change all that at runtime by hooking different “implementors”.

    Reply

Leave a Reply Cancel reply

Fill in your details below or click an icon to log in:

spacer
spacer

You are commenting using your WordPress.com account. ( Log Out / Change )

spacer

You are commenting using your Twitter account. ( Log Out / Change )

spacer

You are commenting using your Facebook account. ( Log Out / Change )

You are commenting using your Google+ account. ( Log Out / Change )

Cancel

Connecting to %s

« Previous Post
Next Post »
Follow

Get every new post delivered to your Inbox.

Build a website with WordPress.com
gipoco.com is neither affiliated with the authors of this page nor responsible for its contents. This is a safe-cache copy of the original web site.