Reputation: 4176
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!
Upvotes: 2
Views: 557
Reputation: 59811
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
.
Upvotes: 1
Reputation: 9559
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);
}
}
Upvotes: 3