Callback
Basic Callbacks
Callbacks can be described as a very generalized form of function pointers. Each Callback represents some kind of action (usually calling a certain function or a certain object method) that can be invoked at any time.
When you need some kind of Action-Reaction behavior, callbacks are very handy. Typical example is when you have some button in your GUI application. When a user clicks on that button (action) you wont some part of your code to be executed (reaction). You tell button witch part of code, in some yours function, to execute via callback object.
This tutorial will clear some basic things in U++ callback mechanics. Here is code of our example
#include <Core/Core.h>
struct Foo {
int x;
void Action() { Cout() << "Action: " << x << '\n'; }
void ActionWithParam(int y) { Cout() << "ActionWithParam: " << x + y << '\n'; }
Callback WhenDo;
void Do() { WhenDo(); }
Foo(int x = 0) : x(x) {}
};
void Fn()
{
Cout() << "Fn!" << '\n';
}
struct Bar : Pte<Bar> {
Foo foo;
void Action() { Cout() << "foo's Do called\n"; }
typedef Bar CLASSNAME;
Bar() { foo.WhenDo = THISBACK(Action); }
};
struct Safe : Pte<Safe> {
void Action() { Cout() << "safe action!\n"; }
};
CONSOLE_APP_MAIN
{
Foo a(10);
Callback cb1 = callback(&a, &Foo::Action);
Callback cb2 = callback(Fn);
Callback1<int> cb3 = callback(&a, &Foo::ActionWithParam);
Callback cb4 = callback1(&a, &Foo::ActionWithParam, 30);
cb1();
cb2();
cb3(10);
cb4();
Cout() << "---------\n";
cb4 << cb2;
cb4();
Cout() << "---------\n";
Bar b;
b.foo.Do();
/*Cout() << "---------\n";
{
Safe f;
cb4 = pteback(&f, &Safe::Action);
Cout() << "callback valid: " << (bool)cb4 << '\n';
cb4();
}
Cout() << "callback valid: " << (bool)cb4 << '\n';
cb4();*/
}
First we declared Foo class, and the only interesting thing in this class is that it has one callback member object WhenDo, and method Do() in which WhenDo object () operator is called. This operator will call WhenDo.Execute() which executes callback with a given set of arguments.
Then we have global function Fn() that just prints "Fn!" to console.
We then derive two class Bar and Safe from Pte class templates. Pte adds necessary functionality to object so that Ptr smart pointer can point to that object. Ptr and Pte gives smart pointers system with two important characteristics: pointers are cleared when pointed object is destructed and pointers have persistence features.
Bar have one member object foo, and in it constructor we assign to foo's WhenDo new callback object which when executed calls bar's Action method.
That's it, now lets put our new classes to show:). In CONSOLE_APP_MAIN we first have
Foo a(10);
Callback cb1 = callback(&a, &Foo::Action);
Callback cb2 = callback(Fn);
Callback1<int> cb3 = callback(&a, &Foo::ActionWithParam);
Callback cb4 = callback1(&a, &Foo::ActionWithParam, 30);
cb1();
cb2();
cb3(10);
cb4();
We have 4 variants of Callback objects. When cb1 is executed it will call a.Action(). cb2 will call global function Fn(). cb3 is little more interesting, notice that it is of type Callback1, and this callback enable us to call function with one parameter, in this example when you execute cb3(10) callback will call a.ActionWithParam(10). cb4 will also call ActionWithParam but this time parameter is saved directly in callback object, so when you execute cb4() it will always call a.ActionWithParam(30).
Actually U++ Callbacks support up to 4 parameters (it can be expande later, but this seems to be enough). Respective types are Callback, Callback1, Callback2, Callback3, Callback4. Note that 1-4 are templates (types of parameters). "callback" is an overloaded function that is capable of creating all of these based on actuall parameter used.
Next is
cb4 << cb2;
cb4();
Operator << demonstrate one nice feature of U++ callbacks, so call chaining of callbacks. Basically it means, create new callback that invokes actions of cb4 and cb2, and then store that callback in cb4. So when we execute cb4() it will call a.ActionWithParam(30) and then Fn().
Next few lines are
Bar b;
b.foo.Do();
We create one Bar object and then calls foo's Do which executes foo's WhenDo callback, so on the end b.Action() will be executed - feel little lost here:), don't be callbacks are just a way to tell some object what to do in different situations.
On end of our main function we have next block of code
{
Safe f;
cb4 = pteback(&f, &Safe::Action);
Cout() << "callback valid: " << (bool)cb4 << '\n';
cb4();
}
Cout() << "callback valid: " << (bool)cb4 << '\n';
cb4();
In inner scope we construct f and then create callback to f.Action(). pteback is similar to callback method, but pteback will create a callback that can be safly invoked even after destruction of f object, and in order to work f must be ancestor of Pte.
In Cout line we see nice way of telling if callback object is valid (can invoke some action) or invalid. Callback have overloaded operator bool() so when we cast some callback object to bool this method will return True if callback can invoke some action.
Then in the end of inner scope we execute cb4. Now when we get out of the inner scope, f object will be destroyed and cb4 will have pointer to destroyed object, so every execution of cb4 callback would be dangerous. But thanks to U++ smart pointer system it is safe to execute cb4 callback - nothing happens like it should.
Just to be sure, here is the output of our program
|
Action: 10
Fn!
ActionWithParam: 20
ActionWithParam: 40
---------
ActionWithParam: 40
Fn!
---------
foo's Do called
|
|
|
As you see, U++ callbacks are really simple to use, with nice and clean interfaces, yet they are very powerful.
main.cpp
#include <Core/Core.h>
using namespace Upp;
struct Foo {
int x;
void Action() { Cout() << "Action: " << x << '\n'; }
void ActionWithParam(int y) { Cout() << "ActionWithParam: " << x + y << '\n'; }
Callback WhenDo;
void Do() { WhenDo(); }
Foo(int x = 0) : x(x) {}
};
void Fn()
{
Cout() << "Fn!" << '\n';
}
struct Bar {
Foo foo;
void Action() { Cout() << "foo's Do called\n"; }
typedef Bar CLASSNAME;
Bar() { foo.WhenDo = THISBACK(Action); }
};
struct Safe : Pte<Safe> {
void Action() { Cout() << "safe action!\n"; }
};
CONSOLE_APP_MAIN
{
Foo a(10);
Callback cb1 = callback(&a, &Foo::Action);
Callback cb2 = callback(Fn);
Callback1<int> cb3 = callback(&a, &Foo::ActionWithParam);
Callback cb4 = callback1(&a, &Foo::ActionWithParam, 30);
cb1();
cb2();
cb3(10);
cb4();
Cout() << "---------\n";
cb4 << cb2;
cb4();
Cout() << "---------\n";
Bar b;
b.foo.Do();
Cout() << "---------\n";
{
Safe f;
cb4 = pteback(&f, &Safe::Action);
Cout() << "callback valid: " << (bool)cb4 << '\n';
cb4();
}
Cout() << "callback valid: " << (bool)cb4 << '\n';
cb4();
}
|