Boost.Signals2 bietet eine Klasse boost::signals2::signal
an, mit der Sie ein Signal erstellen. Die Klasse ist in der Headerdatei boost/signals2/signal.hpp
definiert. Da mit boost/signals2.hpp
eine Headerdatei zur Verfügung steht, die alle in Boost.Signals2 definierten Klassen und Funktionen verfügbar macht, reicht es, wenn Sie diese Headerdatei einbinden.
Die Klasse signal
befindet sich im Namensraum boost::signals2
. Alle Klassen und Funktionen von Boost.Signals2 sind ohne Ausnahme in diesem Namensraum definiert.
boost::signals2::signal
#include <boost/signals2.hpp>
#include <iostream>
using namespace boost::signals2;
int main()
{
signal<void()> s;
s.connect([]{ std::cout << "Hello, world!\n"; });
s();
}
Die Klasse boost::signals2::signal
ist ein Template und erwartet die Signatur der Funktionen, die als Ereignisverarbeiter verwendet werden, als Template-Parameter. Im Beispiel 67.1 können mit dem Signal s nur Funktionen verknüpft werden, deren Signatur zu void()
kompatibel ist.
Über die Methode connect()
wird eine Lambda-Funktion mit dem Signal s verknüpft. Das funktioniert, weil die Lambda-Funktion kompatibel zur Signatur void()
ist. Die Lambda-Funktion ist ein Ereignisverarbeiter, der aufgerufen wird, wenn das Signal s ausgelöst wird.
Anschließend wird auf s so zugegriffen, als würde es sich um eine Funktion handeln. Die Signatur entspricht dabei dem Template-Parameter: Die runden Klammern bleiben leer. Es werden keine Parameter übergeben, weil die Signatur void()
keine Parameter erwartet.
Durch den Aufruf von s wird ein Signal ausgelöst, das zum Aufruf der Lambda-Funktion führt, weil diese Funktion mit connect()
mit dem Signal verknüpft ist.
Beispiel 67.1 kann genauso gut mit std::function
umgesetzt werden.
std::function
#include <functional>
#include <iostream>
int main()
{
std::function<void()> f;
f = []{ std::cout << "Hello, world!\n"; };
f();
}
Im Beispiel 67.2 wird bei einem Aufruf von f die verknüpfte Lambda-Funktion aufgerufen. Während mit std::function
nicht viel mehr möglich ist, können wie im Beispiel 67.3 mehrere Ereignisverarbeiter mit einem Signal vom Typ boost::signals2::signal
verknüpft werden.
boost::signals2::signal
#include <boost/signals2.hpp>
#include <iostream>
using namespace boost::signals2;
int main()
{
signal<void()> s;
s.connect([]{ std::cout << "Hello"; });
s.connect([]{ std::cout << ", world!\n"; });
s();
}
Sie können für ein Objekt vom Typ boost::signals2::signal
die Methode connect()
mehrfach aufrufen und mehrere Funktionen mit einem Signal verknüpfen. Wird das Signal ausgelöst, werden die Funktionen in der Reihenfolge ausgeführt, in der sie mit connect()
mit dem Signal verknüpft wurden.
Die Methode connect()
ist überladen, so dass Sie alternativ explizit angeben können, in welcher Reihenfolge Funktionen aufgerufen werden sollen. Dazu wird connect()
als zusätzlicher Parameter ein int
-Wert übergeben.
#include <boost/signals2.hpp>
#include <iostream>
using namespace boost::signals2;
int main()
{
signal<void()> s;
s.connect(1, []{ std::cout << ", world!\n"; });
s.connect(0, []{ std::cout << "Hello"; });
s();
}
Beispiel 67.4 gibt wie das vorherige Hello, world!
auf die Standardausgabe aus.
Es ist nicht nur möglich, mit connect()
eine Funktion mit einem Signal zu verbinden, sondern diese Verbindung mit disconnect()
wieder zu lösen.
boost::signals2::signal
lösen#include <boost/signals2.hpp>
#include <iostream>
using namespace boost::signals2;
void hello() { std::cout << "Hello"; }
void world() { std::cout << ", world!\n"; }
int main()
{
signal<void()> s;
s.connect(hello);
s.connect(world);
s.disconnect(world);
s();
}
Beispiel 67.5 gibt lediglich Hello
aus, weil vor Aussenden des Signals die Verknüpfung mit der Funktion world()
gelöst wurde.
Neben connect()
und disconnect()
bietet die Klasse boost::signals2::signal
nur wenige weitere Methoden an. Sehen Sie sich dazu Beispiel 67.6 an.
boost::signals2::signal
angebotene Methoden#include <boost/signals2.hpp>
#include <iostream>
using namespace boost::signals2;
int main()
{
signal<void()> s;
s.connect([]{ std::cout << "Hello"; });
s.connect([]{ std::cout << ", world!"; });
std::cout << s.num_slots() << '\n';
if (!s.empty())
s();
s.disconnect_all_slots();
}
num_slots()
gibt die Anzahl der verknüpften Funktionen zurück. Soll überprüft werden, ob keine Funktionen verknüpft sind, kann empty()
aufgerufen werden. Die Methode disconnect_all_slots()
wiederum löst alle verknüpften Funktionen vom Signal.
#include <boost/signals2.hpp>
#include <iostream>
using namespace boost::signals2;
int main()
{
signal<int()> s;
s.connect([]{ return 1; });
s.connect([]{ return 2; });
std::cout << *s() << '\n';
}
Im Beispiel 67.7 sind zwei Lambda-Funktionen mit einem Signal s verknüpft. Die erste Lambda-Funktion gibt 1 zurück, die zweite 2.
Wenn Sie das Beispiel ausführen, wird 2
auf die Standardausgabe ausgegeben. Die Rückgabewerte der Lambda-Funktionen werden von s entgegengenommen, bis auf den letzten Rückgabewert aber ignoriert. Standardmäßig wird lediglich der letzte Rückgabewert aller aufgerufenen Funktionen von einem Signal zurückgegeben.
Beachten Sie, dass s()
den Rückgabewert der letzten aufgerufenen Funktion nicht direkt zurückgibt. Stattdessen wird ein Objekt vom Typ boost::optional
zurückgegeben, das dereferenziert werden muss, um die Zahl 2 zu erhalten. Boost.Signals2 greift auf boost::optional
zu, weil beim Auslösen eines Signals, mit dem keine Funktionen verknüpft sind, keine Rückgabewerte zur Verfügung stehen. In diesem Fall wird ein leeres Objekt vom Typ boost::optional
zurückgegeben. boost::optional
wird im Kapitel 21 vorgestellt.
Es ist möglich, ein Signal so anzupassen, dass Rückgabewerte anderweitig verarbeitet werden. Dazu muss der Klasse boost::signals2::signal
als zweiter Template-Parameter ein Combiner übergeben werden.
#include <boost/signals2.hpp>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace boost::signals2;
template <typename T>
struct min_element
{
typedef T result_type;
template <typename InputIterator>
T operator()(InputIterator first, InputIterator last) const
{
std::vector<T> v(first, last);
return *std::min_element(v.begin(), v.end());
}
};
int main()
{
signal<int(), min_element<int>> s;
s.connect([]{ return 1; });
s.connect([]{ return 2; });
std::cout << s() << '\n';
}
Ein Combiner ist eine Klasse, die den Operator operator()
überlädt. Diese Methode wird automatisch aufgerufen und bekommt zwei Iteratoren übergeben, mit denen auf die mit dem Signal verbundenen Funktionen zugegriffen werden kann. Diese werden beim Dereferenzieren der Iteratoren aufgerufen, so dass ihre Rückgabewerte im Combiner zur Verfügung stehen. Dort kann beispielsweise wie im Beispiel 67.8 ein Algorithmus aus der Standardbibliothek verwendet werden, um den kleinsten Wert zu ermitteln und zurückzugeben.
Der Combiner, der von boost::signals2::signal
standardmäßig verwendet wird, ist boost::signals2::optional_last_value
. Dieser Combiner gibt wie bereits erläutert ein Objekt vom Typ boost::optional
zurück, das leer ist, wenn ein Signal ausgelöst wird, mit dem keine Funktion verknüpft ist. Wird ein Combiner selbst definiert, kann für den Rückgabewert ein beliebiger Typ verwendet werden. So verwendet der Combiner min_element
im Beispiel 67.8 als Typ des Rückgabewerts den Template-Parameter, der bei der Instanziierung in main()
an min_element
übergeben wird.
Es ist nicht möglich, einen Algorithmus wie std::min_element()
direkt als Template-Parameter an boost::signals2::signal
zu übergeben. Die Klasse boost::signals2::signal
erwartet, dass der Combiner einen Typ result_type
definiert. Dieser Typ beschreibt den Rückgabewert von operator()
. Fehlt er wie bei Algorithmen aus der Standardbibliothek, meldet der Compiler einen Fehler.
Beachten Sie, dass es außerdem im Beispiel 67.8 nicht möglich ist, die Iteratoren first und last direkt an std::min_element()
zu übergeben. Dieser Algorithmus erwartet Forward-Iteratoren, während Combiner mit Input-Iteratoren arbeiten. Das Beispiel nimmt deswegen einen Umweg über einen Vektor, der alle Rückgabewerte speichert, bevor der kleinste Wert mit std::min_element()
ermittelt wird.
Im Beispiel 67.9 wird der Combiner so geändert, dass Rückgabewerte nicht ausgewertet werden, sondern in einem Container zurückgegeben werden.
#include <boost/signals2.hpp>
#include <vector>
#include <algorithm>
#include <iostream>
using namespace boost::signals2;
template <typename T>
struct return_all
{
typedef T result_type;
template <typename InputIterator>
T operator()(InputIterator first, InputIterator last) const
{
return T(first, last);
}
};
int main()
{
signal<int(), return_all<std::vector<int>>> s;
s.connect([]{ return 1; });
s.connect([]{ return 2; });
std::vector<int> v = s();
std::cout << *std::min_element(v.begin(), v.end()) << '\n';
}
Entwickeln Sie ein Programm, in dem Sie eine Klasse button
definieren. Diese Klasse soll eine anklickbare Schaltfläche in einer grafischen Benutzeroberfläche repräsentieren. Fügen Sie dieser Klasse die Methoden add_handler()
und remove_handler()
hinzu, denen eine Funktion übergeben werden können soll. Wenn eine Methode click()
aufgerufen wird, sollen die registrierten Funktionen nacheinander aufgerufen werden. Erstellen Sie eine Instanz der Klasse button
und testen Sie sie, indem Sie in einem Ereignisverarbeiter eine Meldung in die Standardausgabe schreiben. Rufen Sie click()
auf, um einen Mausklick auf die Schaltfläche zu simulieren.