Some time ago I wrote a class that has the ability to use a callback. I remeber testing it and thinking, "Cool it works" and that was that.
Now, years later I find I need to use such functionality and I've forgotten how to do the callback thing.
// ***************************************************************
// Base class for an object that can be drawn on the screen.
// Possibly clicked on.
// ***************************************************************
class drawObj : public rect, public dblLinkListObj {
public:
drawObj();
drawObj(rect* inRect,bool inClicks=false);
drawObj(int inLocX, int inLocY, int inWidth,int inHeight,bool inClicks=false);
virtual ~drawObj();
virtual bool wantRefresh(void);
virtual void setNeedRefresh(void);
virtual void setLocation(int x,int y);
virtual void draw(void); // Call this one. Don't inherit this one.
virtual void drawSelf(void); // Inherit this one and make it yours.
virtual void setFocus(bool setLoose); // We are either getting or loosing focus.
void clickable(bool inWantsClicks);
virtual bool acceptClick(point where);
virtual void clickOver(void);
virtual void doAction(void); // Override me for action!
void setCallback(void(*funct)(void)); // Or use a callback?
protected:
bool needRefresh;
bool focus;
bool wantsClicks;
bool clicked;
void (*callback)(void);
};
Its the last method on the list..
The trouble is how to use it?
closeBtn* aCloseBtn = new closeBtn(5,293);
aCloseBtn->begin();
aCloseBtn->setCallback(&breakout::closeCallback()); //<<- What is the syntax for this?
addObj(aCloseBtn);
Breakout is just the main class that is a breakout game. The callback is when someone clicks the "X" go away button and it needs to call the method to set the bool "needToClose" No interrupts or anything fancy like that. Both just member functions of different objects.
I've been playing around with ideas for a common set of buttons to do different functions. One of the main ones is to close the widow/process. Do I do it nicely and let them install a callback? Or just go the the base class and call its close() method. Basically pulling the plug on the poor thing.
The callback just seems too complicated. I'm leaning toward calling the base class's close() and be done with it.
You are using virtual methods and multiple inheritance. The next logical step is to use interfaces which would make what you are trying to do quite easy.
#include <iostream>
class MyClass;
struct Callbacks { // This is an abstract class that provides pure virtual callback methods (interface in Java)
virtual void callbackA(MyClass &instance) = 0;
virtual void callbackB(MyClass &instance) = 0;
virtual ~Callbacks() = default; // Always use a virtual destructor if you're going to inherit from a class, because you might be deleting an instance using a base class pointer
};
class MyClass {
private:
const int id;
Callbacks *callbacks = nullptr;
public:
MyClass(int id) : id{id} {}
int getID() const { return id; }
void doActionA() {
if (callbacks)
callbacks->callbackA(*this);
}
void doActionB() {
if (callbacks)
callbacks->callbackB(*this);
}
void setCallbacks(Callbacks *callbacks) { this->callbacks = callbacks; }
void setCallbacks(Callbacks &callbacks) { setCallbacks(&callbacks); }
};
struct MyCallbacks : public Callbacks { // This is an implementation of the callbacks
void callbackA(MyClass &instance) override {
std::cout << "Callback A called from instance #" << instance.getID() << std::endl;
}
void callbackB(MyClass &instance) override {
std::cout << "Callback B called from instance #" << instance.getID() << std::endl;
}
};
int main() {
MyCallbacks cb;
MyClass instance1 = 1;
MyClass instance2 = 2;
instance1.setCallbacks(cb);
instance2.setCallbacks(cb);
instance1.doActionA();
instance1.doActionB();
instance2.doActionB();
instance2.doActionA();
}
This prints:
Callback A called from instance #1
Callback B called from instance #1
Callback B called from instance #2
Callback A called from instance #2
Do note that the Callbacks object must outlive the MyClass instances!
A possible solution is to make a bidirectional relationship, i.e. the callbacks have a pointer to their MyClass instance. Then in the constructor of Callbacks, set the MyClass callbacks to null.
Alternatively, keep all MyClass instances in a linked list, and in the Callbacks destructor, traverse the list, and clear the callbacks of all MyClass instances that have this Callbacks instance as their callbacks.
For example:
#include <iostream>
#include <memory>
class MyClass;
struct Callbacks { // This is an abstract class that provides virtual callback functions (interface in Java)
virtual void callback(MyClass &instance) = 0;
virtual ~Callbacks();
};
class MyClass {
private:
const int id;
Callbacks *callbacks = nullptr;
MyClass *next;
static MyClass *head;
public:
MyClass(int id) : id{id} {
// prepend this to linked list
this->next = head;
head = this;
}
~MyClass() {
// remove this from linked list
for (MyClass **p = &head; *p != nullptr; p = &(*p)->next)
if (*p == this) {
*p = this->next;
break;
}
}
int getID() const { return id; }
void doAction() {
if (callbacks)
callbacks->callback(*this);
else
std::cout << "Instance #" << getID() << " has no callbacks" << std::endl;
}
void setCallbacks(Callbacks *callbacks) { this->callbacks = callbacks; }
void setCallbacks(Callbacks &callbacks) { setCallbacks(&callbacks); }
static void removeCallbacksFromAll(Callbacks *callbacks) {
for (MyClass *p = head; p != nullptr; p = p->next)
if (p->callbacks == callbacks)
p->callbacks = nullptr;
}
};
MyClass *MyClass::head = nullptr;
Callbacks::~Callbacks() {
MyClass::removeCallbacksFromAll(this);
}
struct MyCallbacks : public Callbacks { // This is an implementation of the callbacks
void callback(MyClass &instance) override {
std::cout << "Callback called from instance #" << instance.getID() << std::endl;
}
};
int main() {
auto cb1 = std::make_unique<MyCallbacks>();
auto cb2 = std::make_unique<MyCallbacks>();
MyClass instance1 = 1;
MyClass instance2 = 2;
instance1.setCallbacks(cb1.get());
instance2.setCallbacks(cb2.get());
instance1.doAction();
instance2.doAction();
std::cout << "Deleting callback #1" << std::endl;
cb1.reset(); // Deleting the first callback should remove it from instance1 as well
instance1.doAction();
instance2.doAction();
}
Prints:
Callback called from instance #1
Callback called from instance #2
Deleting callback #1
Instance #1 has no callbacks
Callback called from instance #2