C++ libsigcplusplus — typesafe callbacks
Table of contents
Motivation
There are many situations in which it is desirable to decouple code that detects an event, and the code that deals with it. This is especially common in GUI programming, where a toolkit might provide user interface elements such as clickable buttons but, being a generic toolkit, doesn’t know how an individual application using that toolkit should handle the user clicking on it.
In C the callbacks are generally handled by the application calling a ‘register’ function and passing a pointer to a function and a void* argument, eg.
void clicked(void* data);
button* okbutton = create_button("ok");
static char somedata[] = "This is some data I want the clicked() function to have";
register_click_handler(okbutton, clicked, somedata);
When clicked, the toolkit will call clicked()
with the data pointer
passed to the register_click_handler()
function.
This works in C, but is not typesafe. There is no compile-time way
of ensuring that clicked()
isn’t expecting a struct of some sort
instead of a char *
.
As C++ programmers, we want type safety. We also want to be able to use things other than free-standing functions as callbacks.
libsigc++
provides the concept of a slot, which holds a reference
to one of the things that can be used as a callback:
- A free-standing function as in the example
- A functor object that defines
operator()
(a lambda expression is such an object) - A pointer-to-a-member-function and an instance of an object on
which to invoke it (the object should inherit from
sigc::trackable
)
All of which can take different numbers and types of arguments.
To make it easier to construct these, libsigc++ provides the
sigc::ptr_fun()
and sigc::mem_fun()
functions, for creating slots
from static functions and member functions, respectively. They
return a generic signal::slot
type that can be invoked with emit()
or operator()
.
For the other side of the fence, libsigc++ provides signals, to which the client can attach slots. When the signal is emitted, all the connected slots are called.
Debian installation
sudo apt install libsigc++-3.0-dev
#include <sigc++/sigc++.h>
#include <unistd.h>
#include <iostream>
class AlienDetector {
public:
AlienDetector() {}
void run() {
sleep(3); // wait for aliens
signal_detected.emit(); // panic!
}
sigc::signal<void()> signal_detected;
};
// Using a member function
class AlienAlerter : public sigc::trackable {
public:
AlienAlerter([[maybe_unused]] char const* servername) {}
void alert() {
std::cout << "Member function: There are aliens in the carpark!"
<< std::endl;
}
};
void warn_people() {
std::cout << "Standalone function: There are aliens in the carpark!"
<< std::endl;
}
int main([[maybe_unused]] int argc, [[maybe_unused]] char** argv) {
AlienDetector mydetector;
AlienAlerter myalerter("localhost"); // added
// mydetector.signal_detected.connect( sigc::ptr_fun(warn_people) );
// You can use a lambda expression instead of sigc::ptr_fun().
mydetector.signal_detected.connect([]() { warn_people(); });
mydetector.signal_detected.connect(
sigc::mem_fun(myalerter, &AlienAlerter::alert)); // changed
mydetector.run();
return 0;
}
Possible output
Standalone function: There are aliens in the carpark!
Member function: There are aliens in the carpark!