2

I've built a small, limited-scope, cross-platform UI library in C++. It uses the following classes to handle UI callbacks:

// Base class for templated __Callback class to allow for passing __Callback
// objects as parameters, storing in containers, etc. (as Callback*)
class Callback
{
    public:
        virtual void execute() = 0;
        virtual ~Callback() { }

    protected:
        Callback() { }
};

As the comment describes, this is the base class for callbacks - which allows for passing them as arguments and storing them inside UI widgets such that the proper callback can be executed (when, for example, a user clicks a button).

// C++ has no callbacks (in the sense of an action [method] of a
// target [object]), so we had to roll our own. This class can be used
// directly, but the NEW_CALLBACK macro is easier.
template <class __TargetType>
class __Callback : public Callback
{
    public:
        typedef __TargetType TargetType;
        typedef void (TargetType::*ActionType)(void);

        virtual void execute()
        {
            (this->__target->*this->__action)();
        }

        __Callback(TargetType* target_, ActionType action_) :
        Callback(), __target(target_), __action(action_) { }

        virtual ~__Callback() { }

    private:
        // target object for the callback
        TargetType* __target;

        // action (method) of the target that will be called
        ActionType __action;
};

This templated class is the meat of the callback paradigm. It stores a pointer to an object and a pointer to a member function, such that the member function can be called on the target object at a later time.

#define NEW_CALLBACK(class_, obj_, act_) \
    new __Callback<class_>(obj_, &class_::act_)

This macro just makes it a little easier to create a templated __Callback object.

This has been working great for a long while! A button with a callback might be instantiated like:

MyClass* obj = new MyClass();
Button* btn = new Button("Title", NEW_CALLBACK(MyClass, obj, btnClicked));

This would create a button, to be placed in a window or other container at a later time, and when clicked it will call obj->btnClicked().

Now, my question (sorry for the lengthy setup, I don't think I could pare it down any more than this). A case has arisen where I need to copy a Callback* object. Of course, since it's just a pointer to the base class, I can't determine the type of the templated derived class.

How would one go about copying an arbitrary Callback object, with the copy pointing to the same target and action as the original? Or, is there an entirely different approach to this callback problem that I should be taking (though I would prefer not to change it too much)?

Thanks all!

inspector-g
  • 4,146
  • 2
  • 24
  • 33
  • Is the type erasure a strict requirement on `Callback`? Unrelated: Two leading underscores in identifiers are reserved for the implementation. – pmr May 12 '12 at 00:31
  • 1
    Holy crap, please absolutely refrain from naming your types and variables like that! Any identifier with double underscore (`__this` or `even__this`) or leading underscore + capital letter (`_This`) is reserved for the implementation, and it kinda feels like you're doubly breaking that rule. – Xeo May 12 '12 at 00:50
  • I didn't realize this convention/limitation existed, thanks for pointing it out. I have been utilizing double-underscore prefixes for private members, single for protected members, and none for public. Similarly, single underscore suffixes for arguments and double for return-by-reference. My example is simplified; the real source code for this project is scoped inside a couple namespaces, and most identifiers are prefixed with a project code (e.g. "__abcMember"). In any event, do you have a good link for describing this limitation + any scoping considerations, etc? – inspector-g May 12 '12 at 01:02
  • @inspector-g: http://stackoverflow.com/questions/228783/what-are-the-rules-about-using-an-underscore-in-a-c-identifier – Mankarse May 12 '12 at 01:42
  • Namespaces and scoping are irrelevant, since macros completely ignore them. If the implementation defines a macro that unexpectedly has the same name as your members or anything, you're in trouble. – Xeo May 12 '12 at 13:35

2 Answers2

3

I don't know if there's a better approach that you should take, but this seems like an ideal use for a clone method.

You simply need to define a copy constructor in your __Callback template, define a pure virtual Clone method in your base class, and then implement that virtual method in your template (making use of the copy constructor that you've created)

For example:

class Callback
{
    public:
    ...
    virtual Callback* Clone()=0;
};

template <class __TargetType>
class __Callback : public Callback
{
public:
    ...
    __Callback(const __Callback& other) :
        __target(other.target_), __action(other.action_) { }

    virtual Callback* Clone()
    {
        return new __Callback(this);
    }
}
obmarg
  • 9,369
  • 36
  • 59
1

Use clone as a replacement for virtual constructors. Notice the co-variant return types that make this really work. e.g.

struct Callback {
  virtual Callback* clone() const;
};

template<...>
struct Callback_impl {
  virtual Callback_impl* clone() const;
};

You should also think about shared_ptr for lifetime management. All this seems a little fragile.

To me it looks like you want std::function . It is polymorphic, type-safe and works with pointers to member functions through std::mem_fn.

pmr
  • 58,701
  • 10
  • 113
  • 156
  • Thanks for the link and bit of code! Both answers have been helpful, but, as they say, there can be only one. In regards to `shared_ptr`, I couldn't agree more. I have been reading about them and similar paradigms lately, but haven't used them. As this project is so far along, I recently decided to toy around with them for a bit outside of it to familiarize myself and then utilize them in a subsequent version. – inspector-g May 12 '12 at 01:07
  • I added something I thought about this morning. – pmr May 12 '12 at 10:50