Die Boost C++ Bibliotheken

Geteiltes Eigentum

Der Smartpointer boost::shared_ptr ähnelt boost::scoped_ptr. Der entscheidende Unterschied ist, dass boost::shared_ptr nicht exklusiver Eigentümer eines Objekts ist, sondern mit anderen Smartpointern vom Typ boost::shared_ptr das Eigentum teilen kann. Das Objekt, dessen Adresse in mehreren Kopien eines Smartpointers vom Typ boost::shared_ptr gespeichert ist, wird erst dann zerstört, wenn die letzte Kopie zerstört wird. Da boost::shared_ptr das Eigentum teilen kann, können Kopien dieses Smartpointers erstellt werden – etwas, was mit boost::scoped_ptr nicht möglich ist.

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

Beispiel 1.3. boost::shared_ptr in Aktion
#include <boost/shared_ptr.hpp>
#include <iostream>

int main()
{
  boost::shared_ptr<int> p1{new int{1}};
  std::cout << *p1 << '\n';
  boost::shared_ptr<int> p2{p1};
  p1.reset(new int{2});
  std::cout << *p1.get() << '\n';
  p1.reset();
  std::cout << std::boolalpha << static_cast<bool>(p2) << '\n';
}

Im Beispiel 1.3 werden zwei Smartpointer p1 und p2 vom Typ boost::shared_ptr verwendet. p2 wird mit p1 initialisiert, woraufhin sich beide Smartpointer das Eigentum an der int-Variablen mit dem Wert 1 teilen. Wenn für p1 reset() aufgerufen wird, wird eine neue int-Variable in p1 verankert. Dies bedeutet nicht, dass die bereits existierende int-Variable zerstört wird. Da diese auch in p2 verankert ist, existiert sie weiterhin. Nach dem ersten Aufruf von reset() ist p1 alleiniger Eigentümer einer int-Variablen mit dem Wert 2 und p2 alleiniger Eigentümer einer int-Variablen mit dem Wert 1.

boost::shared_ptr verwendet intern einen Referenzzähler. Erst wenn boost::shared_ptr feststellt, dass die letzte Kopie eines Smartpointers zerstört wird, wird das entsprechende Objekt mit delete freigegeben.

boost::shared_ptr überlädt ähnlich wie boost::scoped_ptr die Operatoren operator*(), operator->() und operator bool(). Darüberhinaus stehen die Methoden get() und reset() zur Verfügung, um die gespeicherte Adresse abzurufen und eine neue Adresse zu setzen.

Dem Konstruktor von boost::shared_ptr kann als zweiter Parameter ein Deleter übergeben werden. Dabei muss es sich um eine Funktion oder ein Funktionsobjekt handeln, das als einzigen Parameter einen Zeiger auf den Typ erwartet, mit dem boost::shared_ptr instanziiert ist. Der Deleter wird im Destruktor von boost::shared_ptr anstelle von delete verwendet. So ist es möglich, andere Ressourcen als dynamisch reservierte Objekte in einem boost::shared_ptr zu verwalten.

Beispiel 1.4. boost::shared_ptr mit benutzerdefiniertem Deleter
#include <boost/shared_ptr.hpp>
#include <Windows.h>

int main()
{
  boost::shared_ptr<void> handle(OpenProcess(PROCESS_SET_INFORMATION, FALSE,
    GetCurrentProcessId()), CloseHandle);
}

Im Beispiel 1.4 wird boost::shared_ptr mit dem Typ void instanziiert. Als ersten Parameter wird dem Konstruktor der Rückgabewert von OpenProcess() übergeben. Es handelt sich hierbei um eine Windows-Funktion, um einen Handle auf einen Prozess zu erhalten. Über den Aufruf von OpenProcess() wird im Beispiel ein Handle auf den eigenen Prozess erhalten – auf das Beispiel selber.

Handle stellen unter Windows Verweise auf Ressourcen dar, die geschlossen werden müssen, wenn sie nicht mehr benötigt werden. Dazu stellt Windows die Funktion CloseHandle() zur Verfügung. Dieser muss als einziger Parameter der Handle für die Ressource übergeben werden, die geschlossen werden soll. Im Beispiel wird CloseHandle() als zweiter Parameter an den Konstruktor von boost::shared_ptr übergeben. CloseHandle() stellt den Deleter von handle dar. Wenn handle am Ende von main() zerstört wird, greift der Destruktor auf CloseHandle() zu und gibt die Ressource frei, deren Handle als erster Parameter an den Konstruktor übergeben wurde.

Anmerkung

Beachten Sie, dass das Beispiel nur deswegen funktioniert, weil ein Handle als void* definiert ist. Würde OpenProcess() nicht den Rückgabewert void* haben und CloseHandle() keinen Parameter vom Typ void* erwarten, könnte boost::shared_ptr nicht mit void instanziiert werden. boost::shared_ptr ist kein Allheilmittel, das sich dank Deleter zur Verwaltung beliebiger Ressourcen eignet.

Beispiel 1.5. boost::make_shared in Aktion
#include <boost/make_shared.hpp>
#include <typeinfo>
#include <iostream>

int main()
{
  auto p1 = boost::make_shared<int>(1);
  std::cout << typeid(p1).name() << '\n';
  auto p2 = boost::make_shared<int[]>(10);
  std::cout << typeid(p2).name() << '\n';
}

Boost.SmartPointers bietet in der Headerdatei boost/make_shared.hpp eine Hilfsfunktion boost::make_shared() an. Sie können mit boost::make_shared() Smartpointer vom Typ boost::shared_ptr erstellen, ohne auf diese Klasse direkt zugreifen und den Konstruktor aufrufen zu müssen.

boost::make_shared() hat den Vorteil, dass der Speicher für das dynamisch zu reservierende Objekt und für den vom Smartpointer verwendeten Referenzzähler in einem Block reserviert werden kann. boost::make_shared() ist effizienter, als wenn Sie das dynamisch zu reservierende Objekt mit new erstellen und der Konstruktor von boost::shared_ptr ein weiteres Mal auf new zugreift, um Speicher für den Referenzzähler zu reservieren.

Sie können boost::make_shared() auch für Arrays verwenden. So führt der zweite Aufruf von boost::make_shared() im Beispiel 1.5 dazu, dass in p2 ein int-Array mit zehn Elementen verankert wird.

Beachten Sie, dass es erst seit Boost 1.53.0 möglich ist, boost::shared_ptr für Arrays zu verwenden. Boost.SmartPointers bietet mit boost::shared_array einen Smartpointer an, der sich zu boost::shared_ptr verhält wie boost::scoped_array zu boost::scoped_ptr. Wenn Sie obiges Beispiel mit Visual C++ 2013 und Boost 1.53.0 oder neuer ausführen, wird für p2 class boost::shared_ptr<int [0]> ausgegeben. Seit Boost 1.53.0 unterstützt boost::shared_ptr sowohl einzelne Objekte als auch Arrays und erkennt, ob die Freigabe im Destruktor mit delete oder delete[] erfolgen muss. Da boost::shared_ptr seit Boost 1.53.0 außerdem den Operator operator[] anbietet, stellt dieser Smartpointer eine Alternative zu boost::shared_array dar.

Beispiel 1.6. boost::shared_array in Aktion
#include <boost/shared_array.hpp>
#include <iostream>

int main()
{
  boost::shared_array<int> p1{new int[1]};
  {
    boost::shared_array<int> p2{p1};
    p2[0] = 1;
  }
  std::cout << p1[0] << '\n';
}

boost::shared_array ergänzt boost::shared_ptr: Da boost::shared_array im Destruktor delete[] ausführt, kann dieser Smartpointer für dynamisch reservierte Arrays verwendet werden. Vor Boost 1.53.0 musste boost::shared_array für Arrays verwendet werden – boost::shared_ptr unterstützte Arrays nicht.

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

Im Beispiel 1.6 teilen sich die Smartpointer p1 und p2 das Eigentum an einem dynamisch reservierten int-Array. Wenn über operator[] auf p2 zugegriffen wird, um den Wert 1 im Array zu speichern, kann über p1 auf das gleiche dynamisch reservierte Array zugegriffen werden. Das Beispiel gibt entsprechend 1 auf die Standardausgabe aus.

boost::shared_array verwendet wie boost::shared_ptr einen Referenzzähler. Das dynamisch reservierte Array wird nicht freigegeben, wenn der Gültigkeitsbereich von p2 endet. Zu diesem Zeitpunkt existiert noch eine Referenz von p1 auf das Array. Erst am Ende von main(), wenn der Gültigkeitsbereich von p1 endet, wird das Array zerstört.

boost::shared_array bietet ebenfalls die Methoden get() und reset() an. Außerdem ist der Operator operator bool überladen.

Beispiel 1.7. boost::shared_ptr mit BOOST_SP_USE_QUICK_ALLOCATOR
#define BOOST_SP_USE_QUICK_ALLOCATOR
#include <boost/shared_ptr.hpp>
#include <iostream>
#include <ctime>

int main()
{
  boost::shared_ptr<int> p;
  std::time_t then = std::time(nullptr);
  for (int i = 0; i < 1000000; ++i)
    p.reset(new int{i});
  std::time_t now = std::time(nullptr);
  std::cout << now - then << '\n';
}

Beachten Sie, dass es von Vorteil sein kann, Smartpointer wie boost::shared_ptr denen aus der Standardbibliothek vorzuziehen. Boost.SmartPointers bietet Makros an, um das Verhalten der Smartpointer zu optimieren. So wird im Beispiel 1.7 das Makro BOOST_SP_USE_QUICK_ALLOCATOR verwendet, um einen in Boost.SmartPointers integrierten Allokator zu aktivieren. Der Allokator reserviert und verwaltet Speicherblöcke, um die Anzahl der Aufrufe von new und delete zu minimieren, die zur Allokation von Referenzzählern benötigt werden. Im Beispiel wird mit std::time() die Zeit vor und nach der Schleife gemessen. Während die gemessene Ausführungsgeschwindigkeit vom verwendeten Computer abhängt, kann das Programm mit BOOST_SP_USE_QUICK_ALLOCATOR schneller sein als ohne. BOOST_SP_USE_QUICK_ALLOCATOR wird in der Dokumentation der Boost-Bibliothek nicht erwähnt. Sie sollten daher die Ausführungsgeschwindigkeit Ihres Programms ausführlich messen und die Ergebnisse vergleichen, die Sie mit und ohne BOOST_SP_USE_QUICK_ALLOCATOR erhalten.

Tipp

Neben BOOST_SP_USE_QUICK_ALLOCATOR unterstützt Boost.SmartPointers weitere Makros wie zum Beispiel BOOST_SP_ENABLE_DEBUG_HOOKS. Die Namen der Makros beginnen mit BOOST_SP_, so dass eine Suche in den Headerdateien von Boost.SmartPointers einen raschen Überblick über die unterstützten Makros verschafft.