Die Boost C++ Bibliotheken

Verbindungen

Sie können Funktionen mit Hilfe der Methoden connect() und disconnect(), die von boost::signals2::signal angeboten werden, verwalten. Da connect() einen Rückgabewert vom Typ boost::signals2::connection besitzt, geht dies auch anderweitig.

Beispiel 67.10. Verküpfungen mit boost::signals2::connection verwalten
#include <boost/signals2.hpp>
#include <iostream>

int main()
{
  boost::signals2::signal<void()> s;
  boost::signals2::connection c = s.connect(
    []{ std::cout << "Hello, world!\n"; });
  s();
  c.disconnect();
}

Anstatt für boost::signals2::signal disconnect() aufzurufen und als Parameter einen Funktionszeiger zu übergeben, kann wie im Beispiel 67.10 geschehen für boost::signals2::connection disconnect() aufgerufen werden, ohne dass der entsprechende Funktionszeiger übergeben werden muss.

Um eine Funktion kurzzeitig zu blockieren, ohne die Verknüpfung mit dem Signal zu lösen, kann auf boost::signals2::shared_connection_block zugegriffen werden.

Beispiel 67.11. Verknüpfungen mit shared_connection_block blockieren
#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  connection c = s.connect([]{ std::cout << "Hello, world!\n"; });
  s();
  shared_connection_block b{c};
  s();
  b.unblock();
  s();
}

Im Beispiel 67.11 wird die Lambda-Funktion zweimal ausgeführt. Obwohl das Signal s dreimal ausgelöst wird, findet beim zweiten Mal kein Aufruf der Lambda-Funktion statt. Denn vor diesem Aufruf wird ein Objekt vom Typ boost::signals2::shared_connection_block erstellt, das die Verbindung mit der Lambda-Funktion blockiert. Die Blockierung wird automatisch aufgehoben, wenn der Gültigkeitsbereich endet und das Objekt b zerstört wird. Sie können die Blockierung auch explizit aufheben, indem Sie die Methode unblock() aufrufen. Da dies im Beispiel 67.11 geschieht, wird beim Auslösen des Signals in der letzten Zeile von main() die Lambda-Funktion wieder aufgerufen.

Neben unblock() bietet boost::signals2::shared_connection_block zwei weitere Methoden block() und blocking() an. Erstere kann verwendet werden, um eine Verbindung nach einem Aufruf von unblock() wieder zu blockieren. Letztere ermöglicht, über den Rückgabewert vom Typ bool abzufragen, ob die Verbindung im Moment blockiert ist.

Beachten Sie, dass die Klasse boost::signals2::shared_connection_block shared im Namen trägt. So können mehrere Objekte vom Typ boost::signals2::shared_connection_block mit der gleichen Verbindung instanziiert werden.

Beispiel 67.12. Eine Verknüpfung mehrfach blockieren
#include <boost/signals2.hpp>
#include <iostream>

using namespace boost::signals2;

int main()
{
  signal<void()> s;
  connection c = s.connect([]{ std::cout << "Hello, world!\n"; });
  shared_connection_block b1{c, false};
  {
    shared_connection_block b2{c};
    std::cout << std::boolalpha << b1.blocking() << '\n';
    s();
  }
  s();
}

Im Beispiel 67.12 wird zweimal auf s zugegriffen. Beim ersten Zugriff findet kein Aufruf der Lambda-Funktion statt, beim zweiten schon. Wenn Sie das Beispiel ausführen, wird Hello, world! einmal ausgegeben.

Das erste Objekt vom Typ boost::signals2::shared_connection_block blockiert die Verbindung zum Signal s nicht, weil dem Konstruktor als zweiter Parameter false übergeben wird. Der Aufruf von blocking() für das Objekt b1 gibt entsprechend false zurück.

Dennoch wird beim ersten Zugriff auf s die Lambda-Funktion nicht ausgeführt. Der Zugriff findet nach der Instanziierung eines zweiten Objekts vom Typ boost::signals2::shared_connection_block statt. Da dem Konstruktor kein zweiter Parameter übergeben wird, blockiert dieses Objekt die Verbindung. Die Blockierung wird automatisch aufgehoben, wenn der Gültigkeitsbereich von b2 endet, so dass beim zweiten Zugriff auf s die Lambda-Funktion ausgeführt wird.

Boost.Signals2 bietet die Möglichkeit, eine Verbindung automatisch zu lösen, wenn ein Objekt zerstört wird, dessen Methode mit einem Signal verbunden ist.

Beispiel 67.13. Methoden als Ereignisverarbeiter verwenden
#include <boost/signals2.hpp>
#include <memory>
#include <functional>
#include <iostream>

using namespace boost::signals2;

struct world
{
  void hello() const
  {
    std::cout << "Hello, world!\n";
  }
};

int main()
{
  signal<void()> s;
  {
    std::unique_ptr<world> w(new world());
    s.connect(std::bind(&world::hello, w.get()));
  }
  std::cout << s.num_slots() << '\n';
  s();
}

Im Beispiel 67.13 wird eine Methode eines Objekts mit einem Signal verbunden. Dies geschieht mit Hilfe von std::bind(). Das Problem ist jedoch, dass das entsprechende Objekt zerstört wird, bevor das Signal ausgelöst wird. Da kein Objekt vom Typ world an std::bind() übergeben wurde, sondern lediglich dessen Adresse, wird beim Aufruf von s() über einen Zeiger auf ein Objekt zugegriffen, das nicht mehr existiert.

Es ist möglich, das Programm so anzupassen, dass die Verbindung automatisch gelöst wird, wenn das Objekt zerstört wird.

Beispiel 67.14. Verknüpfte Methoden automatisch lösen
#include <boost/signals2.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>

using namespace boost::signals2;

struct world
{
  void hello() const
  {
    std::cout << "Hello, world!\n";
  }
};

int main()
{
  signal<void()> s;
  {
    boost::shared_ptr<world> w(new world());
    s.connect(signal<void()>::slot_type(&world::hello, w.get()).track(w));
  }
  std::cout << s.num_slots() << '\n';
  s();
}

Wenn Sie Beispiel 67.14 ausführen, stellen Sie fest, dass num_slots() 0 zurückgibt. Beim Auslösen des Signals wird nicht versucht, eine Methode für ein Objekt aufzurufen, das bereits zerstört wurde. Die Änderungen, die dazu im Programm notwendig sind: Das Objekt vom Typ world muss in einem Smartpointer vom Typ boost::shared_ptr verankert werden, der als Parameter an track() übergeben wird. Diese Methode wird für den Slot aufgerufen, der an connect() übergeben wird, um mitzuteilen, dass das entsprechende Objekt verfolgt werden soll.

Als Slot wird die Funktion oder Methode bezeichnet, die mit einem Signal verknüpft ist. Ein entsprechender Typ, um einen derartigen Slot zu beschreiben, ist in den bisherigen Beispielprogrammen nicht verwendet worden. Das war nicht nötig, weil eine Lambda-Funktion oder ein Zeiger auf eine Funktion oder Methode direkt an connect() übergeben werden kann. Der entsprechende Slot wurde im Signal erstellt und musste nicht anderweitig bearbeitet werden.

Im Beispiel 67.14 wird track() für den Slot aufgerufen, um den Smartpointer mit diesem zu verbinden. Der Typ des Slots hängt vom Signal ab – die Klasse boost::signals2::signal stellt daher einen Typ slot_type zur Verfügung, um auf den benötigten Typ zugreifen zu können. Da sich slot_type außerdem wie std::bind() verhält, können im Beispiel 67.14 die beiden Parameter, die den Slot beschreiben, direkt übergeben werden. Für den entsprechenden Slot, der daraufhin erstellt wird, kann track() aufgerufen werden, um den Slot mit einem Smartpointer vom Typ boost::shared_ptr zu verknüpfen. Über diesen Smartpointer wird das Objekt verfolgt, was bedeutet: Der Slot wird automatisch entfernt, wenn das im Smartpointer verankerte Objekt zerstört wird.

Slots bieten neben track() auch eine Methode track_foreign() an, die verwendet werden kann, wenn Objekte in Smartpointern anderer Typen verwaltet werden. Während track() einen Smartpointer vom Typ boost::shared_ptr erwartet, kann track_foreign() verwendet werden, um beispielsweise einen Smartpointer vom Typ std::shared_ptr zu übergeben. Andere Smartpointer als std::shared_ptr und std::weak_ptr müssen Boost.Signals2 explizit bekannt gemacht werden, damit sie an track_foreign() übergeben werden können.

Boost.Signals2 erlaubt es, in einem Ereignisverarbeiter auf das Objekt vom Typ boost::signals2::signal zuzugreifen, um neue Funktionen zu verknüpfen oder bestehende Verbindungen zu lösen.

Beispiel 67.15. Im Ereignisverarbeiter neue Verknüpfungen erstellen
#include <boost/signals2.hpp>
#include <iostream>

boost::signals2::signal<void()> s;

void connect()
{
  s.connect([]{ std::cout << "Hello, world!\n"; });
}

int main()
{
  s.connect(connect);
  s();
}

Im Beispiel 67.15 wird innerhalb der Funktion connect() auf s zugegriffen, um eine Lambda-Funktion mit genau diesem Signal zu verknüpfen. Da connect() aufgerufen wird, wenn das Signal ausgelöst wird, stellt sich die Frage, ob auch die Lambda-Funktion aufgerufen wird.

Wenn Sie Beispiel 67.15 ausführen, stellen Sie fest, dass keine Ausgabe erfolgt. Die Lambda-Funktion wird nicht aufgerufen. Boost.Signals2 unterstützt das Verknüpfen von Funktionen mit Signalen, während Ereignisverarbeiter ausgeführt werden, die mit diesen Signalen verküpft sind. Neu verknüpfte Funktionen werden aber erst dann ausgeführt, wenn das Signal erneut ausgelöst wird.

Beispiel 67.16. Im Ereignisverarbeiter Verknüpfungen lösen
#include <boost/signals2.hpp>
#include <iostream>

boost::signals2::signal<void()> s;

void hello()
{
  std::cout << "Hello, world!\n";
}

void disconnect()
{
  s.disconnect(hello);
}

int main()
{
  s.connect(disconnect);
  s.connect(hello);
  s();
}

Im Beispiel 67.16 wird keine neue Funktion im Ereignisverarbeiter verknüpft, sondern eine bestehende Verbindung gelöst. Auch hier stellt sich die Frage, ob hello() aufgerufen wird oder nicht.

Auch dieses Beispiel gibt nichts auf die Standardausgabe aus. hello() wird nicht aufgerufen.

Sie können sich diese Verhaltensweise erklären, wenn Sie sich vorstellen, dass beim Auslösen eines Signals eine temporäre Kopie aller Slots erstellt wird. Neu mit einem Signal verknüpfte Funktionen werden nicht dieser temporären Kopie hinzugefügt und werden deswegen erst aufgerufen, wenn das Signal neu ausgelöst wird. Verknüpfungen, die aufgelöst wurden, befinden sich zwar in dieser Kopie. Nur wird beim Dereferenzieren der Iteratoren im Combiner überprüft, ob die Verknüpfung gelöst wurde oder nicht. Ein Auflösen der Verknüpfung wird sofort berücksichtigt, was nicht unwichtig ist, wenn das Signal zum Beispiel mit der Methode eines Objekts verknüpft war und dieses zerstört wurde.