Die Boost C++ Bibliotheken

Spezielle Smartpointer

Alle in diesem Kapitel kennengelernten Smartpointer können für sich allein genommen in unterschiedlichen Situationen verwendet werden. Der Smartpointer vom Typ boost::weak_ptr ergibt jedoch nur im Zusammenspiel mit boost::shared_ptr Sinn. Er ist in der Headerdatei boost/weak_ptr.hpp definiert.

Beispiel 1.8. boost::weak_ptr in Aktion
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <thread>
#include <functional>
#include <iostream>

void reset(boost::shared_ptr<int> &sh)
{
  sh.reset();
}

void print(boost::weak_ptr<int> &w)
{
  boost::shared_ptr<int> sh = w.lock();
  if (sh)
    std::cout << *sh << '\n';
}

int main()
{
  boost::shared_ptr<int> sh{new int{99}};
  boost::weak_ptr<int> w{sh};
  std::thread t1{reset, std::ref(sh)};
  std::thread t2{print, std::ref(w)};
  t1.join();
  t2.join();
}

Die Klasse boost::weak_ptr muss mit einem boost::shared_ptr initialisiert werden. Die wichtigste von ihr angebotene Methode ist lock(). lock() gibt einen boost::shared_ptr zurück, der das Eigentum mit dem Smartpointer teilt, mit dem boost::weak_ptr initialisiert wurde. Für den Fall, dass der boost::shared_ptr, der bei der Initialisierung angegeben wurde, nicht mehr auf ein Objekt zeigt, ist der von lock() zurückgegebene boost::shared_ptr leer.

boost::weak_ptr bietet sich an, wenn eine Funktion mit einem in einem boost::shared_ptr verwalteten Objekt arbeiten soll, die Lebensdauer dieses Objekts jedoch nicht von dieser Funktion abhängen soll. Die Funktion soll nur dann mit dem Objekt arbeiten, wenn es momentan in Smartpointern vom Typ boost::shared_ptr, die an anderen Stellen im Programm existieren, verankert ist. Verschwinden diese Smartpointer, soll das entsprechende Objekt nicht mehr existieren und nicht unnötigerweise durch einen zusätzlichen Smartpointer vom Typ boost::shared_ptr in der entsprechenden Funktion am Leben gehalten werden.

Im Beispiel 1.8 werden in main() zwei Threads erstellt. Ein Thread führt die Funktion reset() aus, die eine Referenz auf einen boost::shared_ptr erhält. Der andere Thread besteht aus der Funktion print(), die eine Referenz auf einen boost::weak_ptr erhält. Dieser boost::weak_ptr wird in der Funktion main() mit dem boost::shared_ptr initialisiert.

Wenn das Programm gestartet wird, werden die beiden Funktionen reset() und print() gleichzeitig in zwei Threads ausgeführt. Es lässt sich nicht vorhersagen, in welcher Reihenfolge die verschiedenen Anweisungen in den Threads abgearbeitet werden. Das Problem ist, dass in der Funktion reset() ein Objekt zerstört werden soll, das gleichzeitig in einem anderen Thread verarbeitet und auf die Standardausgabe ausgegeben werden soll. Es könnte sein, dass in der Funktion reset() das Objekt zerstört wird, während in der Funktion print() auf dieses Objekt zugegriffen wird, um es auszugeben.

boost::weak_ptr löst diese Problematik wie folgt: Beim Aufruf von lock() wird ein boost::shared_ptr zurückgeben. Dieser boost::shared_ptr zeigt auf ein Objekt, wenn dieses Objekt zum Zeitpunkt des Aufrufs existiert. Andernfalls ist der boost::shared_ptr leer und ein Null-Zeiger.

boost::weak_ptr allein hat keinen Einfluss auf die Lebensdauer eines Objekts. Damit in print() gefahrlos auf das Objekt zugegriffen kann, ohne dass es in einem anderen Thread zerstört wird, gibt lock() einen boost::shared_ptr zurück. Wird in einem anderen Thread versucht, das Objekt zu zerstören, existiert es dank des von lock() zurückgegebenen Smartpointers vom Typ boost::shared_ptr weiter.

Beispiel 1.9. boost::intrusive_ptr in Aktion
#include <boost/intrusive_ptr.hpp>
#include <atlbase.h>
#include <iostream>

void intrusive_ptr_add_ref(IDispatch *p) { p->AddRef(); }
void intrusive_ptr_release(IDispatch *p) { p->Release(); }

void check_windows_folder()
{
  CLSID clsid;
  CLSIDFromProgID(CComBSTR{"Scripting.FileSystemObject"}, &clsid);
  void *p;
  CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER, __uuidof(IDispatch), &p);
  boost::intrusive_ptr<IDispatch> disp{static_cast<IDispatch*>(p), false};
  CComDispatchDriver dd{disp.get()};
  CComVariant arg{"C:\\Windows"};
  CComVariant ret{false};
  dd.Invoke1(CComBSTR{"FolderExists"}, &arg, &ret);
  std::cout << std::boolalpha << (ret.boolVal != 0) << '\n';
}

int main()
{
  CoInitialize(0);
  check_windows_folder();
  CoUninitialize();
}

Der Smartpointer boost::intrusive_ptr funktioniert genauso wie boost::shared_ptr. Während boost::shared_ptr jedoch automatisch mitzählt, wie viele Kopien momentan auf ein Objekt verweisen und sich das Eigentum teilen, müssen bei boost::intrusive_ptr Sie mitzählen. Das ist zum Beispiel von Vorteil, wenn auf Klassen aus Frameworks zugegriffen wird, die sowieso schon mitzählen.

boost::intrusive_ptr ist in der Headerdatei boost/intrusive_ptr.hpp definiert.

Im Beispiel 1.9 wird auf Microsoft COM-Funktionen zugegriffen – das Beispiel kann ausschließlich unter Windows ausgeführt werden. COM-Objekte bieten sich als Beispiel für boost::intrusive_ptr an, da sie mitzählen, wie viele Zeiger momentan auf sie verweisen. Der interne Referenzzähler eines COM-Objekts kann über die Methoden AddRef() und Release() jeweils um 1 erhöht und verringert werden. Wird der interne Zähler nach einem Aufruf von Release() auf 0 gesetzt, wird das COM-Objekt automatisch zerstört.

Die beiden Methoden AddRef() und Release() werden innerhalb der Funktionen intrusive_ptr_add_ref() und intrusive_ptr_release() aufgerufen, um den internen Zähler im COM-Objekt zu inkrementieren und dekrementieren. Boost.SmartPointers erwartet, dass Sie diese beiden Funktionen zur Verfügung stellen. Sie werden automatisch aufgerufen, wenn der interne Zähler inkrementiert oder dekrementiert werden muss. Der Parameter, der an diese Funktionen übergeben wird, ist ein Zeiger auf den Typ, mit dem das Template boost::intrusive_ptr instanziiert wurde.

Das COM-Objekt, das im Beispiel 1.9 verwendet wird, heißt FileSystemObject und ist standardmäßig unter Windows vorhanden. Es gestattet einen Zugriff aufs Dateisystem, um zum Beispiel zu überprüfen, ob ein bestimmtes Verzeichnis existiert. Im Beispiel wird überprüft, ob es das Verzeichnis C:\Windows gibt. Wie dies im Detail funktioniert, hängt von COM ab und ist unwichtig, um die Funktionsweise von boost::intrusive_ptr zu verstehen. Entscheidend ist, dass am Ende der Funktion check_windows_folder(), wenn der Gültigkeitsbereich von disp endet, die Funktion intrusive_ptr_release() automatisch aufgerufen wird. Nachdem dort mit Release() der interne Zähler auf 0 gesetzt wird, wird das COM-Objekt FileSystemObject zerstört.

Der Parameter false, der an den Konstruktor von boost::intrusive_ptr übergeben wird, verhindert, dass intrusive_ptr_add_ref() aufgerufen wird. Da ein COM-Objekt, wenn es mit CoCreateInstance() erstellt wird, einen bereits auf 1 gesetzten Referenzzähler hat, darf dieser nicht noch einmal in intrusive_ptr_add_ref() inkrementiert werden.