C plus plus:Modern C plus plus:Storing Functors

From GPWiki
Jump to: navigation, search

Modern C++ : Going Beyond "C with Classes"

Introduction

Functors are wonderful with algorithms. Since the algorithms are templated on the actual type of the Functor, anything with an operator() works. This is faster and more convenient than having to use some object derived from an interface class, but it does make Functors rather non-trivial to store.

Luckily, the template wizards at Boost went and wrote the Boost Function library, which can store arbitrary functors. It's so good, in fact, that it got accepted into TR1.

The function Template

( As with Boost.Bind, I'm going to cover the Boost implementation, since it's nearly universally available )

In the previous article, we used Binders to make a nullary functor to pass to std::generate_n that returned an int in [0,10). To store that, you could use the following:

boost::function< int() > rand_0_10 = boost::bind( std::modulus<int>(), 
                                                  boost::bind(&std::rand), 
                                                  10 );

Now I'd guess that, if you haven't seen boost::function before, you've never seen int() used as a type. ( No, it's not calling the default constructor for an int. )

If you've used function pointers, however, you probably know that the type of &std::rand is int (*)() -- pointer to a function that returns an int and takes zero parameters. Since &, when not overloaded, returns a pointer to the type of something, then you can reasonably deduce, correctly, that the type of std::rand is int(). In C, however, that type is totally useless, so function names are implicitly convertable to pointers to themselves, and this implicit cast in maintained in C++. I will suggest, however, that you get into the habit of using & to obtain a pointer to a function: It's clearer and it's needed for member functions.

Generic Callbacks

Since this is GPWiki, let's pretend we're writing an (overly) simple engine, similar to GLUT. In GLUT, you register a number of callbacks for various different events, and then calling glutMainLoop. For example, you register a function that will be called to display your scene using glutDisplayFunc, which my glut.h defines as follows:

GLUTAPI void GLUTAPIENTRY glutDisplayFunc(void (GLUTCALLBACK *func)(void));

Ignoring the extra #defined tags needed for portability, this function accepts one parameter of type void(*)(), a pointer to a non-member nullary function that doesn't return anything. It'd not all that helpful, but since GLUT uses a C API, it's acceptable. Most people will define some sort of void draw_scene(); function and be quite happy. (Some C APIs include a void* parameter that can be used to, indirectly, have member functions as callbacks, but the method to be described here is nicer for a fully C++ library. If you already have a C API with the void* parameter, however, you can wrap it in a way that uses the C API to call the boost::function.)

Since you're reading this, of course, you're interested in C++, not C. One of C++'s advantages over C is native support for classes, and for most people ( though hopefully not you once you have finished this series ), classes are just about the reason why they're using C++ in the first place. Now, however, you have a big problem. You've designed a wonderful class hierarchy for a revolutionary scene management system, and would like you be able to call a member of the active scene to display it. Unfortunately, a pointer to that member function has a type similar to void(Scene::*)() and needs to be called using either the .* or the ->* operator, neither of which GLUT can handle.

struct Scene {
    virtual void draw() {}
    virtual void key_event(int key, int mousex, int mousey, bool isdown) {}
  protected:
    virtual ~Scene() {}
};
 
// obviously you'd do something more interesting than this in real code
struct TriangleScene : Scene {
    void draw() {
        draw_triangle();
    }
    void key_event(int key, int mousex, int mousey, bool isdown) {
        if ( key == KEY_ESCAPE && isdown ) {
            std::exit(0);
        }
    }
};
 
TriangleScene awesome_triangle_scene;

So instead, our analogous bglt ( Better GL Toolkit ) function would be declared as follows:

void bgltDisplayFunc(boost::function<void()> func);

And defined in the source file something like this:

namespace {
    boost::function<void()> display_callback;
}
void bgltDisplayFunc(boost::function<void()> func) {
    display_callback = func;
}

Now we're in business. We can call bgltDisplayFunc( boost::bind( &Scene::draw, &awesome_triangle_scene ) ); and things will work just fine.

Since function can hold arbitrary functors, a user doesn't need to structure their code to match how the library works. GLUT, for example, uses the following callbacks to do keyboard input:

GLUTAPI void GLUTAPIENTRY glutKeyboardFunc(void (GLUTCALLBACK *func)(unsigned char key, int x, int y));
GLUTAPI void GLUTAPIENTRY glutKeyboardUpFunc(void (GLUTCALLBACK *func)(unsigned char key, int x, int y));
GLUTAPI void GLUTAPIENTRY glutSpecialFunc(void (GLUTCALLBACK *func)(int key, int x, int y));
GLUTAPI void GLUTAPIENTRY glutSpecialUpFunc(void (GLUTCALLBACK *func)(int key, int x, int y));

In order to get function pointers will precisely those types, you need to write a function or each, even if that function just forwards off to how you really wanted to deal with input. Suppose they used function instead:

void bgltKeyboardFunc(boost::function<void (unsigned char key, int x, int y)> func);
void bgltKeyboardUpFunc(boost::function<void (unsigned char key, int x, int y)> func);
void bgltSpecialFunc(boost::function<void (int key, int x, int y)> func);
void bgltSpecialUpFunc(boost::function<void (int key, int x, int y)> func);

This way, the binders can do the forwarding work for us. Supposing we have an int decode_glut_key(int key, bool special) function that converts from GLUT's keycodes to the one's we're using ( since we don't like the "special" key method GLUT uses ), there's no extra function needed to decode the key and pass it off to the void Scene::key_event(int key, int mousex, int mousey, bool isdown) member function:

bgltKeyboardFunc( boost::bind( &Scene::key_event,
                               &awesome_triangle_scene,
                               boost::bind( &decode_glut_key, _1, false),
                               _2,
                               _3,
                               true ) );
bgltKeyboardUpFunc( boost::bind( &Scene::key_event,
                                 &awesome_triangle_scene,
                                 boost::bind( &decode_glut_key, _1, false),
                                 _2,
                                 _3,
                                 false ) );
bgltSpecialFunc( boost::bind( &Scene::key_event,
                              &awesome_triangle_scene,
                              boost::bind( &decode_glut_key, _1, true),
                              _2,
                              _3,
                              true ) );
bgltSpecialUpFunc( boost::bind( &Scene::key_event,
                                &awesome_triangle_scene,
                                boost::bind( &decode_glut_key, _1, true),
                                _2,
                                _3,
                                false ) );

The biggest trick to this one is not being scared. You should be able to see that calling stored_keyboard_func(_1,_2,_3); results in a call to (&awesome_triangle_scene)->key_event( decode_glut_key(_1, false), _2, _3, true );

This example also illustrates 2 important points:

  • Binds can be nested.
    Even though there's a bind inside the bind, the arguments still end up in the right places.
  • Signatures don't need to match exactly.
    With pointers to functions, you cannot pass glutKeyboardFunc a function that takes an int as the first parameter. With function, however, bgltKeyboardFunc doesn't mind that the functor you're giving it has int as a parameter type instead of char. It'll silently use the char-to-int implicit cast just as would have happened had you written out the call yourself.

Performance

Since people generally equate something more general with a loss of efficiency, which is quite often true for programming, I figure it's important that I include this section. Apparently the authors of Boost.Function thought so too, since they included a section about Performance in the documentation.

The most important point mentioned is that, on decent compilers, calling through a boost::function costs the same as a function call through a function pointer, which is even often slightly faster than a virtual function call.

It also specifies that the size of a boost::function is "the size of two function pointers plus one function pointer or data pointer (whichever is larger)". This is larger than a plain function pointer, but since it can actually call member functions, I think that's a fair trade, especially with the current predominance of Object-Oriented game engines. So the question then becomes "is this worse than the other OO-friendly solutions?" Those solutions mean storing a pointer to an object and, optionally, a member function pointer (if you wish to allow any member function name).

Both these methods have signifigant problems. The largest inconvenience is the need to manually write the classes used, since neither the std::lib binders nor the TR1 binders nor the Boost binders generate this type of thing and the classes will need to be derived from a certain interface. You'll need to manage the memory for the instances of these classes, and even the reference count for a nice reference counted smart pointer is larger than the size of a boost::function. Also, for proper lifetime management and polymorphism, the custom class would need to be allocated on the free store, and most implementation don't give out blocks smaller than some size ( often 16 bytes ) on the free store.

If you choose to store the member function pointer, in order to not force all the callbacks to have the same name, then that alone would push the size up to around that of a boost::function since a member function pointer is typically larger than a normal function or data pointer ( in order to store the offset into the virtual table as well ).

Further Reading

If you're interested in the idea and would like to see it fleshed out in more detail, read Herb Sutter's "Generalizing Observer" article from C/C++ Users' Journal.

In Closing

function gives you a fast, simple way of storing functors that's great for doing observer or callbacks in a generic way that works well for both prodedural and object-oriented programs. Combind with some nice Binders, it can also reduce the boilerplate code needed to interact with your callback scheme.

What's Next

Unfortunately, this is the end of the logical progression through the features you need to know for functional-style std::lib usage.

That doesn't mean I'm out of topics to cover, however. The Boost Smart Ptr library, yet another Boost library that's part of TR1, implements a number of different RAII strategies in the form of "smart pointers". Particularly of note is the option of using a fourth copying method: reference counting. This is generally useful enough that I'll be devoting the whole next article to shared_ptr and weak_ptr.