jmasterx
jmasterx

Reputation: 54103

Is a templated, polymorphic callback a good idea?

I'm making a Gui Api for games. The user can always use inheritance on the widget and override, but I want callbacks. I want to use a templated callback system:

so if they want to have one for the mouse they inherit from a version of the templated callback base with mouseargs:

So the base would look like this:

template <typename T>

class AguiEventCallback {

public:
virtual void callback(AguiWidget* sender, T arg) = 0;

};

Is it a good idea to mix templates with polymorphism like this? Would I be better off creating callbacks for each of the types I need (mouse, keyboard, gamepad, etc)?

Thanks

Upvotes: 2

Views: 678

Answers (4)

Emiliano
Emiliano

Reputation: 23712

In addition to other answers here you might have a look to the boost::signal library. It implements a signal/slot mechanism which is indeed useful for GUIs. Performance is not as good as you would expect (costs more than a call to a virtual method) but for a GUI it's just fine.

The boost::signal library can also be used together with boost::bind and this combo is very powerful.

I don't like using inheritance very much for callbacks. It spawns a lot of classes with just 1 method most of the time. It's C++ not Java. you have functions, use them :)

Upvotes: 0

CashCow
CashCow

Reputation: 31435

Using a template the way you have is fine sometimes. There are issues due to the fact that you must give your template a virtual destructor and that

  • If you inline your virtual destructor (as you do with most template functions) some compilers find it hard to stick to the One Definition Rule, particularly if the library is used across libraries.

  • If you do not inline your virtual destructor you have to instantiate every type you are going to use with that template. This is my own preferred approach.

For a callback, you do have the option of using boost::function. This avoids having to derive classes from your template, create them with new and probably stick them into a shared_ptr somewhere. The downside of boost::function as a callback, I have found, is that it is harder to debug into if something goes wrong. Beware of that issue.

Upvotes: 1

Tony Delroy
Tony Delroy

Reputation: 106066

Momentarily accepting your virtual dispatch solution, what your templated approach guarantees is a uniformity in the callback function name and arguments. Sadly, that will force a lot of other code to disambiguate which callback is being invoked / overridden, probably causing more trouble than good.

That said, as janm said other options exist. Functors are more powerful (you can change them on an existing object at run-time, you can have lists of observers) but also have to be initialised at the right time (pure virtual functions effectively remind the programmer to supply them at compile time), and introduce a bigger variety of run-time states to reason about and understand.

You might also be able to use a template policy or the Curiously Recurring Template Pattern to supply behaviours at compile time, allowing inlining, dead code elimination, type-specific behaviours and other optimisations.

Upvotes: 0

janm
janm

Reputation: 18339

Have a look at boost::function and boost::bind. Accept a function object with a defined parameter list for particular events, and callers can do what they want.

This gives callback implementations lots of flexibility and the object generating the events requires even less knowledge of the callback implementation.

For example:

 typedef boost::function<void (AguiWidget* sender)> CallbackFunc;
 void register_callback(CallbackFunc const& f);

And the client:

class Caller {
    void do_register() { register_callback(bind(&Caller::event, this, 123, _1)); }

    void event(int arg, AguiWidget* sender) { ... }
};

Just showing function/bind, many other issues ignored; eg. memory management, object lifetime.

Upvotes: 3

Related Questions