Die Boost C++ Bibliotheken

Signale

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.

Beispiel 67.1. Hello, world! mit 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.

Beispiel 67.2. "Hello, world!" mit 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.

Beispiel 67.3. Mehrere Ereignisverarbeiter mit 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.

Beispiel 67.4. Ereignisverarbeiter mit expliziter Reihenfolge
#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.

Beispiel 67.5. Ereignisverarbeiter von 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.

Beispiel 67.6. Verschiedene von 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.

Beispiel 67.7. Rückgabewert von Ereignisverarbeitern auswerten
#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.

Beispiel 67.8. Kleinsten Rückgabewert mit benutzerdefiniertem Combiner finden
#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.

Beispiel 67.9. Alle Rückgabewerte mit benutzerdefiniertem Combiner erhalten
#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';
}

Aufgabe

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.