SlideShare ist ein Scribd-Unternehmen logo
1 von 11
Kurs: Boost C++-Bibliotheken (http://tutego.de/g/BOOSTCPP/)
Autor: Boris Schäling


Kapitel 2: Smart Pointers
2.1 Allgemeines

In der 1998 verabschiedeten ersten Version des C++ Standards gibt es lediglich einen smart
pointer: std::auto_ptr. Dieser verhält sich grundsätzlich wie ein herkömmlicher Zeiger: Er
speichert eine Adresse, über die auf ein dynamisch reserviertes Objekt zugegriffen werden
kann. Der Zeiger std::auto_ptr wird jedoch als intelligent bzw. smart bezeichnet, weil er
automatisch im Destruktor das Objekt mit delete freigibt, dessen Adresse er speichert. Das
setzt voraus, dass er mit der Adresse eines Objekts initialisiert wird, das mit new erstellt
wurde. Der Vorteil aber ist, dass die Speicherfreigabe mit delete nicht vergessen werden
kann, da sie immer garantiert im Destruktor von std::auto_ptr erfolgt. Dies ist vor allem
im Zusammenhang mit Exceptions wichtig: Ohne smart pointers wie std::auto_ptr wäre es
notwendig, in jeder Funktion, die dynamisch Speicher reserviert, mögliche Ausnahmen
abzufangen, um dann den dynamisch reservierten Speicher freizugeben, bevor die
Ausnahme wieder geworfen und an die aufrufende Funktion weitergereicht wird. Die Boost
C++ Bibliothek Smart Pointers bietet nun zahlreiche weitere smart pointer an, die in
unterschiedlichen Situationen eingesetzt werden können.

2.2 RAII

Smart pointers basieren auf einem Prinzip namens RAII: Resource Acquisition Is Initialization.
Smart pointers sind nur ein Beispiel für RAII, wenn auch ein sehr prominentes. Smart
pointers werden verwendet, um dynamisch reservierten Speicher garantiert wieder
freizugeben - ohne dass ein Entwickler die Freigabe vergessen kann oder die Freigabe wegen
einer Ausnahme nicht ausgeführt wird, weil die Ausführung einer Funktion abgebrochen
wird und die Anweisung zur Speicherfreigabe übersprungen wird. Diese Garantie wird
dadurch erreicht, indem ein smart pointer mit der Adresse eines dynamisch reservierten
Objekts initialisiert wird und im Destruktor die Adresse zur Speicherfreigabe verwendet. Da
der Destruktor immer ausgeführt wird, erfolgt demnach auch immer eine Speicherfreigabe.

RAII wird immer dann angewandt, wenn auf einen Schritt zwingend ein zweiter Schritt
erfolgen muss, um eine im ersten Schritt geöffnete Resource wieder zu schließen. Da in sehr
vielen C++-Programmen Speicher dynamisch verwaltet werden muss, sind smart pointers
wichtige RAII-Klassen. RAII lässt sich aber durchaus auch in anderen Fällen verwenden.

#include <windows.h>

class windows_handle
{
  public:
    windows_handle(HANDLE h)
      : handle_(h)
    {
    }

    ~windows_handle()
{
           CloseHandle(handle_);
       }

       HANDLE handle() const
       {
         return handle_;
       }

     private:
       HANDLE handle_;
};

int main()
{
  windows_handle h(OpenProcess(PROCESS_SET_INFORMATION, FALSE,
                               GetCurrentProcessId()));
  SetPriorityClass(h.handle(), HIGH_PRIORITY_CLASS);
}

Im obigen Beispiel wird eine Klasse windows_handle definiert, die im Destruktor die
Funktion CloseHandle() aufruft. Es handelt sich dabei um eine Windows API-Funktion, so
dass das obige Programm lediglich unter Windows läuft. Die Klasse windows_handle ist
unter Windows insofern nützlich als dass viele Ressourcen unter Windows zuerst geöffnet
werden müssen, bevor sie verwendet werden können. Das Öffnen setzt voraus, dass
Ressourcen, wenn sie nicht mehr benötigt werden, geschlossen werden. Die Klasse
windows_handle soll nun sicherstellen, dass Ressourcen garantiert geschlossen werden.

Ein Objekt vom Typ windows_handle wird mit einem Handle initialisiert. Windows
verwendet Handles, um Ressourcen eindeutig zu identifizieren. Sie erhalten zum Beispiel
einen Handle, wenn Sie die Funktion OpenProcess() aufrufen. Diese gibt einen Handle vom
Typ HANDLE zurück, über den auf einen momentan laufenden Prozess zugegriffen werden
kann. Im obigen Beispiel ist dies der eigene Prozess - also das Programm selbst.

Über den Handle wird in diesem Beispiel die Priorität erhöht, so dass dem Programm unter
Windows mehr Rechenzeit zur Verfügung gestellt wird. Dies geschieht ausschließlich zur
Illustration und macht für obiges Programm nicht wirklich Sinn. Das entscheidende ist
jedoch, dass die Ressource, die mit OpenProcess() geöffnet wurde, in der Funktion main()
nicht mit CloseHandle() geschlossen werden muss. Selbstverständlich würde die Ressource
automatisch geschlossen werden, wenn das Programm beendet wird. In größeren und
komplexeren Programmen würde die Klasse windows_handle aber sicherstellen, dass eine
Ressource dann geschlossen wird, wenn sie nicht mehr benötigt wird. In dem Moment, in
dem der Gültigkeitsbereich des entsprechenden Objekts endet - im obigen Beispiel von h am
Ende der Funktion main() - wird der Destruktor aufgerufen, der automatisch die Ressource
schließt.

2.3 Scoped Pointer

Ein scoped pointer ist ein Zeiger, der alleiniger Eigentümer eines dynamisch reservierten
Objekts ist. Die entsprechende Klasse heißt boost::scoped_ptr und ist in der Headerdatei
boost/scoped_ptr.hpp definiert. Im Unterschied zu std::auto_ptr kann ein scoped
pointer das Eigentum nicht an einen anderen scoped pointer übertragen. Einmal mit einer
Adresse initialisiert wird das dynamisch reservierte Objekt dann freigegeben, wenn der
Destruktor ausgeführt wird.

Da ein scoped pointer einfach nur eine Adresse speichert und alleiniger Eigentümer eines
Objekts ist, ist die Implementation von boost::scoped_ptr einfacher als von
std::auto_ptr. Da das Eigentum nicht wie bei std::auto_ptr auf andere Objekte
übertragen werden kann, kann boost::scoped_ptr die bessere Wahl sein, weil das
Eigentum auch nicht versehentlich übertragen werden kann.

#include <boost/scoped_ptr.hpp>

int main()
{
  boost::scoped_ptr<int> i(new int);
  *i = 1;
  *i.get() = 2;
  i.reset(new int);
}

Der smart pointer boost::scoped_ptr kann lediglich initialisiert werden, um dann über
überladene Operatoren auf das referenzierte Objekt so zuzugreifen, wie Sie es von
herkömmlichen Zeigern gewöhnt sind. Im Detail sind operator*(), operator->() und
operator bool() überladen. Daneben stehen die beiden Methoden get() und reset() zur
Verfügung: Während get() die Adresse des Objekts zurückgibt, auf das der smart pointer
verweist, kann mit reset() eine neue Adresse im smart pointer gespeichert werden. Ein bis
dahin referenziertes Objekt wird automatisch zerstört.

Im Destruktor von boost::scoped_ptr wird das referenzierte Objekt mit delete
freigegeben. Daher dürfen Sie boost::scoped_ptr nicht mit der Adresse eines dynamisch
reservierten Arrays initialisieren, da dieses mit delete[] freigegeben werden müsste. Sie
müssen in diesem Fall die Klasse boost::scoped_array verwenden, die im Folgenden
vorgestellt wird.

2.4 Scoped Array

Ein scoped array wird wie ein scoped pointer verwendet. Der entscheidende Unterschied ist,
dass der Destruktor eines scoped arrays dynamisch reservierten Speicher mit delete[]
freigibt. Da ausschließlich Arrays mit delete[] freigegeben werden, müssen Sie ein scoped
array mit der Adresse eines dynamisch reservierten Arrays initialisieren.

Die entsprechende Klasse für scoped arrays ist boost::scoped_array und befindet sich in
der Headerdatei boost/scoped_array.hpp.

#include <boost/scoped_array.hpp>

int main()
{
  boost::scoped_array<int> i(new int[2]);
  *i.get() = 1;
  i[1] = 2;
  i.reset(new int[3]);
}
Die Klasse boost::scoped_array überlädt die beiden Operatoren operator[]() und
operator bool(). Über operator[]() können Sie direkt auf eine Position im Array
zugreifen - ein Objekt vom Typ boost::scoped_array verhält sich also wie das Array,
dessen Adresse es speichert.

So wie bei boost::scoped_ptr stehen ebenfalls die beiden Methoden get() und reset()
zur Verfügung, mit denen die im boost::scoped_array gespeicherte Adresse abgerufen
und neugesetzt werden kann.

2.5 Shared Pointer

Dieser smart pointer wird in der Praxis sehr häufig eingesetzt und wurde in der ersten
Version des C++ Standards schmerzlich vermisst. Er ist im sogenannten Technical Report 1 in
den C++ Standard aufgenommen worden. Wenn Ihre Entwicklungsumgebung den Technical
Report 1 unterstützt, können Sie die Klasse std::shared_ptr aus der Headerdatei memory
nutzen. In der Boost C++ Bibliothek heißt dieser smart pointer boost::shared_ptr und
befindet sich in der Headerdatei boost/shared_ptr.hpp.

Der smart pointer boost::shared_ptr ist grundsätzlich boost::scoped_ptr sehr ähnlich.
Der entscheidende Unterschied ist, dass boost::shared_ptr nicht exklusiver Eigentümer
eines Objekts ist, sondern mit anderen smart pointern vom Typ boost::shared_ptr das
Eigentum teilen kann: Das Objekt, dessen Adresse in mehreren shared pointern gespeichert
ist, wird erst dann zerstört, wenn der letzte shared pointer zerstört ist.

Da boost::shared_ptr das Eigentum teilen kann, können Kopien dieses smart pointers
erstellt werden - etwas, was mit boost::scoped_ptr nicht funktioniert. Dies erst
ermöglicht, smart pointer in Containern zu speichen - etwas, was auch nicht mit
std::auto_ptr möglich ist, da bei jeder Kopie dieses smart pointers der Eigentümer
wechselt.

#include <boost/shared_ptr.hpp>
#include <vector>

int main()
{
  std::vector<boost::shared_ptr<int> > v;
  v.push_back(boost::shared_ptr<int>(new int(1)));
  v.push_back(boost::shared_ptr<int>(new int(2)));
}

Dank des smart pointers boost::shared_ptr ist es wie oben gezeigt möglich, Adressen von
dynamisch reservierten Objekten sicher in Containern zu speichern. Der Container speichert
zwar Kopien und muss eventuell zusätzliche Kopien erstellen, wenn bereits gespeicherte
Elemente neu arrangiert werden müssen. Da boost::shared_ptr jedoch das Eigentum an
Objekten teilen kann, sind Kopien gleichwertig. Dies ist wie bereits beschrieben bei
std::auto_ptr nicht der Fall, weswegen ein std::auto_ptr auch niemals in einem
Container gespeichert werden sollte.

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

#include <boost/shared_ptr.hpp>

int main()
{
  boost::shared_ptr<int> i1(new int(1));
  boost::shared_ptr<int> i2(i1);
  i1.reset(new int(2));
}

Im obigen Beispiel werden zwei shared pointer i1 und i2 verwendet, die anfangs auf das
gleiche Objekt vom Typ int zeigen. Während die Variable i1 direkt mit einer Adresse
initialiert wird, erhält die Variable i2 über den Copy-Konstruktor die in i1 gespeicherte
Adresse. Wenn dann für i1 die Methode reset() aufgerufen wird, wird die in i1
gespeicherte Adresse neu gesetzt. Das bis dato in i1 gespeicherte Objekt wird jedoch nicht
zerstört, da es noch in der Variablen i2 gespeichert ist. Der smart pointer
boost::shared_ptr zählt also mit, wie viele shared pointer momentan auf ein Objekt
verweisen, und zerstört dieses erst dann, wenn der Gültigkeitsbereich des letzten shared
pointers endet.

Der smart pointer boost::shared_ptr besitzt eine Besonderheit, die es erlaubt anzugeben,
wie ein Objekt zerstört werden soll. Dies erfolgt standardmäßig mit delete, kann jedoch wie
im folgenden Beispiel zu sehen auch anders geschehen.

#include <boost/shared_ptr.hpp>
#include <windows.h>

int main()
{
  boost::shared_ptr<void> h(OpenProcess(PROCESS_SET_INFORMATION, FALSE,
GetCurrentProcessId()), CloseHandle);
  SetPriorityClass(h.get(), HIGH_PRIORITY_CLASS);
}

Dem Konstruktor von boost::shared_ptr kann als zweiter Parameter eine Funktion oder
ein Funktionsobjekt übergeben werden. Im obigen Beispiel ist dies die Windows API-
Funktion CloseHandle(). Endet der Gültigkeitsbereich der Variablen h, wird nicht delete
aufgerufen, sondern die als zweiter Parameter angegebene Funktion. Diese muss mit einem
einzigen Parameter vom Typ HANDLE aufgerufen werden können, damit der Code kompiliert.
Dies ist bei CloseHandle() der Fall.

Dieses Beispiel funktioniert genauso wie das zu Beginn dieses Kapitels vorgestellte
Programm, das RAII veranschaulicht hat. Anstatt eine eigene Klasse windows_handle zu
definieren, wird hier die Besonderheit von boost::shared_ptr ausgenutzt und dem
Konstruktor als zweiter Parameter eine Funktion übergeben, die automatisch aufgerufen
wird, wenn der Gültigkeitsbereich des shared pointers endet.
2.6 Shared Array

Ein shared array funktioniert grundsätzlich wie ein shared pointer. Der entscheidende
Unterschied ist, dass der Destruktor eines shared arrays delete[] aufruft. Das wiederum
bedeutet, dass ein shared array mit der Adresse eines dynamisch reservierten Arrays
initialisiert werden muss - denn nur diese dürfen mit delete[] freigegeben werden. In den
Boost C++ Bibliotheken finden Sie die Klasse boost::shared_array in der Headerdatei
boost/shared_array.hpp.

#include <boost/shared_array.hpp>
#include <iostream>

int main()
{
  boost::shared_array<int> i1(new int[2]);
  boost::shared_array<int> i2(i1);
  i1[0] = 1;
  std::cout << i2[0] << std::endl;
}

Ähnlich wie beim shared pointer kann auch bei einem shared array das Eigentum mit
anderen shared arrays geteilt werden. So teilen sich im obigen Programm die beiden
Variablen i1 und i2 das Eigentum an einem dynamisch reservierten Array. Wenn über
operator[]() auf i1 zugegriffen wird, um den Wert 1 im Array zu speichern, kann über i2
auf das gleiche dynamisch reservierte Array zugegriffen werden und der Wert zum Beispiel
auf die Standardausgabe ausgegeben werden.

So wie alle bisher in diesem Kapitel vorgestellten smart pointer bietet auch
boost::shared_array die Methoden get() und reset() an. Außerdem ist ebenfalls der
Operator operator bool() überladen.

2.7 Weak Pointer

Alle bisher in diesem Kapitel kennengelernten smart pointer können jeweils für sich allein
genommen in unterschiedlichen Situationen verwendet werden. Der weak pointer jedoch
macht nur im Zusammenspiel mit dem shared pointer Sinn. Er ist in der Headerdatei
boost/weak_ptr.hpp als boost::weak_ptr definiert.

#include   <windows.h>
#include   <boost/shared_ptr.hpp>
#include   <boost/weak_ptr.hpp>
#include   <iostream>

DWORD WINAPI reset(LPVOID p)
{
  boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p);
  sh->reset();
  return 0;
}

DWORD WINAPI print(LPVOID p)
{
  boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p);
  boost::shared_ptr<int> sh = w->lock();
if (sh)
      std::cout << *sh << std::endl;
    return 0;
}

int main()
{
  boost::shared_ptr<int> sh(new int(99));
  boost::weak_ptr<int> w(sh);
  HANDLE threads[2];
  threads[0] = CreateThread(0, 0, reset, &sh, 0, 0);
  threads[1] = CreateThread(0, 0, print, &w, 0, 0);
  WaitForMultipleObjects(2, threads, TRUE, INFINITE);
}

Die Klasse boost::weak_ptr muss immer mit einem boost::shared_ptr initialisiert
werden. Einmal initialisiert bietet sie genaugenommen nur eine einzige nützliche Methode
an, nämlich lock(). Diese Methode gibt einen boost::shared_ptr zurück, der das
Eigentum mit dem shared pointer teilt, mit dem der weak pointer initialisiert wurde. Für den
Fall, dass der shared pointer, der bei der Initialisierung angegeben wurde, nicht mehr auf ein
Objekt zeigt, ist auch der von lock() zurückgegebene shared pointer auf 0 gesetzt.

Der weak pointer bietet sich an, wenn eine Funktion mit einem in einem shared pointer
verwalteten Objekt arbeiten soll, die Lebensdauer dieses Objekts jedoch nicht von dieser
Funktion abhängen soll. Die Funktion soll also nur dann mit dem Objekt arbeiten, wenn es
momentan in shared pointern, die an anderen Stellen im Programm existieren, verankert ist.
Werden diese shared pointer resettet, soll das entsprechende Objekt nicht mehr existieren
und nicht unnötigerweise durch einen zusätzlichen shared pointer in der entsprechenden
Funktion am Leben gehalten werden.

Im obigen Beispiel werden in main() zwei Threads erstellt. Dazu wird auf Funktionen
zugegriffen, die vom Betriebssystem Windows zur Verfügung gestellt werden. Obiges
Beispiel lässt sich daher ausschließlich unter Windows kompilieren und ausführen.

Der eine Thread führt die Funktion reset() aus, der ein Zeiger auf einen shared pointer
übergeben wird. Der andere Thread besteht aus der Funktion print(), die einen Zeiger auf
einen weak pointer erhält. Dieser weak pointer ist in der Funktion main() mit dem shared
pointer initialisiert worden.

Wenn das Programm gestartet wird, werden die beiden Funktionen reset() und print()
gleichzeitig in zwei Threads ausgeführt. Es lässt sich dabei nicht vorhersagen, in welcher
Reihenfolge die verschiedenen Anweisungen in den Threads abgearbeitet werden. Das
Problem dabei 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 also sein, dass in der Funktion reset() das Objekt zerstört wird,
während gerade in der Funktion print() genau auf dieses Objekt zugegriffen wird, um es
auszugeben.

Der weak pointer löst diese Problematik wie folgt: Beim Aufruf von lock() wird ein shared
pointer zurückgeben. Dieser shared pointer zeigt auf ein Objekt, wenn dieses Objekt zum
Zeitpunkt des Aufrufes existiert. Andernfalls ist der shared pointer auf 0 gesetzt und somit
ein typischer Null-Zeiger.
Der weak pointer allein hat demnach keinen Einfluss auf die Lebensdauer eines Objekts.
Damit trotzdem in der Funktion print() gefahrlos auf das Objekt zugegriffen kann, ohne
dass es plötzlich in einem anderen Thread zerstört wird, gibt lock() einen shared pointer
zurück. Selbst wenn nun in einem anderen Thread versucht wird, das Objekt zu zerstören,
existiert es dank des von lock() zurückgegebenen shared pointers weiter.

2.8 Intrusive Pointer

Der intrusive pointer funktioniert genauso wie der shared pointer. Während
boost::shared_ptr jedoch automatisch mitzählt, wie viele shared pointer momentan auf
ein Objekt zeigen und das Eigentum teilen, muss beim intrusive pointer der Entwickler
mitzählen. Das ist zum Beispiel dann von Vorteil, wenn auf Klassen aus Frameworks
zugegriffen wird, die ihrerseits sowieso schon mitzählen.

In den Boost C++ Bibliotheken befindet sich der intrusive pointer in der Headerdatei
boost/intrusive_ptr.hpp und heißt boost::intrusive_ptr.

#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));
  CComDispatchDriver dd(disp.get());
  CComVariant arg("C:Windows");
  CComVariant ret(false);
  dd.Invoke1(CComBSTR("FolderExists"), &arg, &ret);
  std::cout << (ret.boolVal != 0) << std::endl;
}

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

Im obigen Beispiel wird auf COM-Funktionen zugegriffen, so dass der Code ausschließlich
unter Windows kompiliert und ausgeführt werden kann. COM-Objekte bieten sich insofern
als Beispiel für boost::intrusive_ptr an als dass sie mitzählen, wie viele Zeiger momentan
auf sie verweisen. Der interne Zä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 nun innerhalb der Funktionen
intrusive_ptr_add_ref() und intrusive_ptr_release() aufgerufen, um den internen
Zähler im COM-Objekt zu inkrementieren und dekrementieren. Das COM-Objekt, das in
diesem Beispiel 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 den
Ordner C:Windows gibt. Wie das 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 des
intrusive pointers 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.



2.9 Pointer Container

Nachdem Sie nun die verschiedenen smart pointer der Boost C++ Bibliotheken
kennengelernt haben, können Sie für dynamisch reservierte Objekte und Arrays sicheren
Code schreiben. Häufig müssen Objekte in Containern verwaltet werden, was wie Sie bereits
gesehen haben mit boost::shared_ptr und boost::shared_array auch problemlos
funktioniert.

#include <boost/shared_ptr.hpp>
#include <vector>

int main()
{
  std::vector<boost::shared_ptr<int> > v;
  v.push_back(boost::shared_ptr<int>(new int(1)));
  v.push_back(boost::shared_ptr<int>(new int(2)));
}

Während der Code im obigen Beispiel völlig korrekt ist und smart pointers so angewandt
werden könnten, ist das wegen der wiederholten Angabe von boost::shared_ptr nicht nur
mehr Schreibarbeit. Das Kopieren von boost::shared_ptr in, aus und innerhalb von
Containern erfordert ein ständiges In- und Dekrementieren von Zählern und ist daher
weniger effizient. Die Boost C++ Bibliotheken bieten daher Pointer Container an, die auf die
Verwaltung dynamisch reservierter Objekte spezialisiert sind.

#include <boost/ptr_container/ptr_vector.hpp>

int main()
{
  boost::ptr_vector<int> v;
  v.push_back(new int(1));
  v.push_back(new int(2));
}
Die Klasse boost::ptr_vector, die in der Headerdatei
boost/ptr_container/ptr_vector.hpp definiert ist, funktioniert genauso wie der im
vorherigen Beispiel verwendete Container, der mit dem Template-Parameter
boost::shared_ptr instantiiert ist. Dadurch, dass boost::ptr_vector auf dynamisch
reservierte Objekte spezialisiert ist, ist diese Klasse effizienter und einfacher anzuwenden.
Da boost::ptr_vector jedoch alleiniger Eigentümer aller Objekte ist, kann nicht wie bei
std::vector<boost::shared_ptr<int> > das Eigentum zum Beispiel mit einem shared
pointer geteilt werden, der nicht im Container gespeichert ist.

Neben boost::ptr_vector stehen weitere Container zur Verfügung, die auf die Verwaltung
dynamisch reservierter Objekte spezialisiert sind. Dazu gehören boost::ptr_deque,
boost::ptr_list, boost::ptr_set, boost::ptr_map, boost::ptr_unordered_set und
boost::ptr_unordered_map. Diese Container entsprechen den aus dem C++ Standard
bekannten Containern. Die letzten beiden genannten Container entsprechen den Containern
std::unordered_set und std::unordered_map, die im Technical Report 1 dem C++
Standard hinzugefügt wurden. Sie sind außerdem als boost::unordered_set und
boost::unordered_map in der Boost C++ Bibliothek Unordered definiert, die Sie einsetzen
können, wenn die von Ihnen verwendete Implementation des C++ Standards den Technical
Report 1 nicht unterstützt.

2.10 Aufgaben

Verbessern Sie folgendes Programm, indem Sie einen geeigneten smart pointer verwenden:

   1.    #include <iostream>
   2.    #include <cstring>
   3.
   4.    char *get(const char *s)
   5.    {
   6.      int size = std::strlen(s);
   7.      char *text = new char[size + 1];
   8.      std::strncpy(text, s, size + 1);
   9.      return text;
   10.   }
   11.
   12.   void print(char *text)
   13.   {
   14.     std::cout << text << std::endl;
   15.   }
   16.
   17.   int main(int argc, char *argv[])
   18.   {
   19.     if (argc < 2)
   20.     {
   21.       std::cerr << argv[0] << " <data>" << std::endl;
   22.       return 1;
   23.     }
   24.
   25.       char *text = get(argv[1]);
   26.       print(text);
   27.       delete[] text;
   28.   }

Verbessern Sie folgendes Programm:
29.   #include <vector>
30.
31.   template <typename T>
32.   T *create()
33.   {
34.     return new T;
35.   }
36.
37.   int main()
38.   {
39.     std::vector<int*> v;
40.     v.push_back(create<int>());
41.   }

Weitere ähnliche Inhalte

Andere mochten auch (12)

Jorge VI
Jorge VIJorge VI
Jorge VI
 
GEM NEWS
GEM NEWSGEM NEWS
GEM NEWS
 
Neohumano relacionismo
Neohumano relacionismoNeohumano relacionismo
Neohumano relacionismo
 
Free lance
Free lanceFree lance
Free lance
 
Larepublica
LarepublicaLarepublica
Larepublica
 
Image Projekt Ried 03
Image Projekt Ried 03Image Projekt Ried 03
Image Projekt Ried 03
 
Willian moreira
Willian moreiraWillian moreira
Willian moreira
 
Practica 7 melissa
Practica 7   melissaPractica 7   melissa
Practica 7 melissa
 
Boletín 13
Boletín 13Boletín 13
Boletín 13
 
Factores Teratogenos
Factores TeratogenosFactores Teratogenos
Factores Teratogenos
 
Unión europea
Unión europeaUnión europea
Unión europea
 
Social Media in der Bildung: Transformation tradierter Lernformen und neue Po...
Social Media in der Bildung: Transformation tradierter Lernformen und neue Po...Social Media in der Bildung: Transformation tradierter Lernformen und neue Po...
Social Media in der Bildung: Transformation tradierter Lernformen und neue Po...
 

Ähnlich wie Schulung C++ Boost Bibliotheken

Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlungroskakori
 
Interprozesskommunikation mit PHP
Interprozesskommunikation mit PHPInterprozesskommunikation mit PHP
Interprozesskommunikation mit PHPStephan Schmidt
 
Workshop zu Hibernate 3.2.2 GA
Workshop zu Hibernate 3.2.2 GAWorkshop zu Hibernate 3.2.2 GA
Workshop zu Hibernate 3.2.2 GAOliver Belikan
 
Drupal 8: TWIG Template Engine
Drupal 8:  TWIG Template EngineDrupal 8:  TWIG Template Engine
Drupal 8: TWIG Template Enginedrubb
 
The Lotus Code Cookbook
The Lotus Code CookbookThe Lotus Code Cookbook
The Lotus Code CookbookUlrich Krause
 

Ähnlich wie Schulung C++ Boost Bibliotheken (7)

Lösungsorientierte Fehlerbehandlung
Lösungsorientierte FehlerbehandlungLösungsorientierte Fehlerbehandlung
Lösungsorientierte Fehlerbehandlung
 
JavaScript Performance
JavaScript PerformanceJavaScript Performance
JavaScript Performance
 
Boost C++ Libraries
Boost C++ LibrariesBoost C++ Libraries
Boost C++ Libraries
 
Interprozesskommunikation mit PHP
Interprozesskommunikation mit PHPInterprozesskommunikation mit PHP
Interprozesskommunikation mit PHP
 
Workshop zu Hibernate 3.2.2 GA
Workshop zu Hibernate 3.2.2 GAWorkshop zu Hibernate 3.2.2 GA
Workshop zu Hibernate 3.2.2 GA
 
Drupal 8: TWIG Template Engine
Drupal 8:  TWIG Template EngineDrupal 8:  TWIG Template Engine
Drupal 8: TWIG Template Engine
 
The Lotus Code Cookbook
The Lotus Code CookbookThe Lotus Code Cookbook
The Lotus Code Cookbook
 

Mehr von tutego

Der C++ Standard
Der C++ StandardDer C++ Standard
Der C++ Standardtutego
 
CSS Seminar
CSS SeminarCSS Seminar
CSS Seminartutego
 
Schulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-BibliothekSchulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-Bibliothektutego
 
SQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-APISQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-APItutego
 
Die JSTL Tag-Library
Die JSTL Tag-LibraryDie JSTL Tag-Library
Die JSTL Tag-Librarytutego
 
Fundamentale Muster in Java
Fundamentale Muster in JavaFundamentale Muster in Java
Fundamentale Muster in Javatutego
 
JdbcTemplate aus Spring
JdbcTemplate aus SpringJdbcTemplate aus Spring
JdbcTemplate aus Springtutego
 
Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing tutego
 
Einführung in den EventBus
Einführung in den EventBusEinführung in den EventBus
Einführung in den EventBustutego
 
Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5tutego
 
Autoboxing in Java 5
Autoboxing in Java 5Autoboxing in Java 5
Autoboxing in Java 5tutego
 

Mehr von tutego (12)

Klassen
KlassenKlassen
Klassen
 
Der C++ Standard
Der C++ StandardDer C++ Standard
Der C++ Standard
 
CSS Seminar
CSS SeminarCSS Seminar
CSS Seminar
 
Schulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-BibliothekSchulung jQuery JavaScript-Bibliothek
Schulung jQuery JavaScript-Bibliothek
 
SQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-APISQL-Updates mit der JDBC-API
SQL-Updates mit der JDBC-API
 
Die JSTL Tag-Library
Die JSTL Tag-LibraryDie JSTL Tag-Library
Die JSTL Tag-Library
 
Fundamentale Muster in Java
Fundamentale Muster in JavaFundamentale Muster in Java
Fundamentale Muster in Java
 
JdbcTemplate aus Spring
JdbcTemplate aus SpringJdbcTemplate aus Spring
JdbcTemplate aus Spring
 
Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing Schieberegler und analoge Anzeigen in Swing
Schieberegler und analoge Anzeigen in Swing
 
Einführung in den EventBus
Einführung in den EventBusEinführung in den EventBus
Einführung in den EventBus
 
Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5Erweiteres for (foreach) in Java 5
Erweiteres for (foreach) in Java 5
 
Autoboxing in Java 5
Autoboxing in Java 5Autoboxing in Java 5
Autoboxing in Java 5
 

Schulung C++ Boost Bibliotheken

  • 1. Kurs: Boost C++-Bibliotheken (http://tutego.de/g/BOOSTCPP/) Autor: Boris Schäling Kapitel 2: Smart Pointers 2.1 Allgemeines In der 1998 verabschiedeten ersten Version des C++ Standards gibt es lediglich einen smart pointer: std::auto_ptr. Dieser verhält sich grundsätzlich wie ein herkömmlicher Zeiger: Er speichert eine Adresse, über die auf ein dynamisch reserviertes Objekt zugegriffen werden kann. Der Zeiger std::auto_ptr wird jedoch als intelligent bzw. smart bezeichnet, weil er automatisch im Destruktor das Objekt mit delete freigibt, dessen Adresse er speichert. Das setzt voraus, dass er mit der Adresse eines Objekts initialisiert wird, das mit new erstellt wurde. Der Vorteil aber ist, dass die Speicherfreigabe mit delete nicht vergessen werden kann, da sie immer garantiert im Destruktor von std::auto_ptr erfolgt. Dies ist vor allem im Zusammenhang mit Exceptions wichtig: Ohne smart pointers wie std::auto_ptr wäre es notwendig, in jeder Funktion, die dynamisch Speicher reserviert, mögliche Ausnahmen abzufangen, um dann den dynamisch reservierten Speicher freizugeben, bevor die Ausnahme wieder geworfen und an die aufrufende Funktion weitergereicht wird. Die Boost C++ Bibliothek Smart Pointers bietet nun zahlreiche weitere smart pointer an, die in unterschiedlichen Situationen eingesetzt werden können. 2.2 RAII Smart pointers basieren auf einem Prinzip namens RAII: Resource Acquisition Is Initialization. Smart pointers sind nur ein Beispiel für RAII, wenn auch ein sehr prominentes. Smart pointers werden verwendet, um dynamisch reservierten Speicher garantiert wieder freizugeben - ohne dass ein Entwickler die Freigabe vergessen kann oder die Freigabe wegen einer Ausnahme nicht ausgeführt wird, weil die Ausführung einer Funktion abgebrochen wird und die Anweisung zur Speicherfreigabe übersprungen wird. Diese Garantie wird dadurch erreicht, indem ein smart pointer mit der Adresse eines dynamisch reservierten Objekts initialisiert wird und im Destruktor die Adresse zur Speicherfreigabe verwendet. Da der Destruktor immer ausgeführt wird, erfolgt demnach auch immer eine Speicherfreigabe. RAII wird immer dann angewandt, wenn auf einen Schritt zwingend ein zweiter Schritt erfolgen muss, um eine im ersten Schritt geöffnete Resource wieder zu schließen. Da in sehr vielen C++-Programmen Speicher dynamisch verwaltet werden muss, sind smart pointers wichtige RAII-Klassen. RAII lässt sich aber durchaus auch in anderen Fällen verwenden. #include <windows.h> class windows_handle { public: windows_handle(HANDLE h) : handle_(h) { } ~windows_handle()
  • 2. { CloseHandle(handle_); } HANDLE handle() const { return handle_; } private: HANDLE handle_; }; int main() { windows_handle h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId())); SetPriorityClass(h.handle(), HIGH_PRIORITY_CLASS); } Im obigen Beispiel wird eine Klasse windows_handle definiert, die im Destruktor die Funktion CloseHandle() aufruft. Es handelt sich dabei um eine Windows API-Funktion, so dass das obige Programm lediglich unter Windows läuft. Die Klasse windows_handle ist unter Windows insofern nützlich als dass viele Ressourcen unter Windows zuerst geöffnet werden müssen, bevor sie verwendet werden können. Das Öffnen setzt voraus, dass Ressourcen, wenn sie nicht mehr benötigt werden, geschlossen werden. Die Klasse windows_handle soll nun sicherstellen, dass Ressourcen garantiert geschlossen werden. Ein Objekt vom Typ windows_handle wird mit einem Handle initialisiert. Windows verwendet Handles, um Ressourcen eindeutig zu identifizieren. Sie erhalten zum Beispiel einen Handle, wenn Sie die Funktion OpenProcess() aufrufen. Diese gibt einen Handle vom Typ HANDLE zurück, über den auf einen momentan laufenden Prozess zugegriffen werden kann. Im obigen Beispiel ist dies der eigene Prozess - also das Programm selbst. Über den Handle wird in diesem Beispiel die Priorität erhöht, so dass dem Programm unter Windows mehr Rechenzeit zur Verfügung gestellt wird. Dies geschieht ausschließlich zur Illustration und macht für obiges Programm nicht wirklich Sinn. Das entscheidende ist jedoch, dass die Ressource, die mit OpenProcess() geöffnet wurde, in der Funktion main() nicht mit CloseHandle() geschlossen werden muss. Selbstverständlich würde die Ressource automatisch geschlossen werden, wenn das Programm beendet wird. In größeren und komplexeren Programmen würde die Klasse windows_handle aber sicherstellen, dass eine Ressource dann geschlossen wird, wenn sie nicht mehr benötigt wird. In dem Moment, in dem der Gültigkeitsbereich des entsprechenden Objekts endet - im obigen Beispiel von h am Ende der Funktion main() - wird der Destruktor aufgerufen, der automatisch die Ressource schließt. 2.3 Scoped Pointer Ein scoped pointer ist ein Zeiger, der alleiniger Eigentümer eines dynamisch reservierten Objekts ist. Die entsprechende Klasse heißt boost::scoped_ptr und ist in der Headerdatei boost/scoped_ptr.hpp definiert. Im Unterschied zu std::auto_ptr kann ein scoped pointer das Eigentum nicht an einen anderen scoped pointer übertragen. Einmal mit einer
  • 3. Adresse initialisiert wird das dynamisch reservierte Objekt dann freigegeben, wenn der Destruktor ausgeführt wird. Da ein scoped pointer einfach nur eine Adresse speichert und alleiniger Eigentümer eines Objekts ist, ist die Implementation von boost::scoped_ptr einfacher als von std::auto_ptr. Da das Eigentum nicht wie bei std::auto_ptr auf andere Objekte übertragen werden kann, kann boost::scoped_ptr die bessere Wahl sein, weil das Eigentum auch nicht versehentlich übertragen werden kann. #include <boost/scoped_ptr.hpp> int main() { boost::scoped_ptr<int> i(new int); *i = 1; *i.get() = 2; i.reset(new int); } Der smart pointer boost::scoped_ptr kann lediglich initialisiert werden, um dann über überladene Operatoren auf das referenzierte Objekt so zuzugreifen, wie Sie es von herkömmlichen Zeigern gewöhnt sind. Im Detail sind operator*(), operator->() und operator bool() überladen. Daneben stehen die beiden Methoden get() und reset() zur Verfügung: Während get() die Adresse des Objekts zurückgibt, auf das der smart pointer verweist, kann mit reset() eine neue Adresse im smart pointer gespeichert werden. Ein bis dahin referenziertes Objekt wird automatisch zerstört. Im Destruktor von boost::scoped_ptr wird das referenzierte Objekt mit delete freigegeben. Daher dürfen Sie boost::scoped_ptr nicht mit der Adresse eines dynamisch reservierten Arrays initialisieren, da dieses mit delete[] freigegeben werden müsste. Sie müssen in diesem Fall die Klasse boost::scoped_array verwenden, die im Folgenden vorgestellt wird. 2.4 Scoped Array Ein scoped array wird wie ein scoped pointer verwendet. Der entscheidende Unterschied ist, dass der Destruktor eines scoped arrays dynamisch reservierten Speicher mit delete[] freigibt. Da ausschließlich Arrays mit delete[] freigegeben werden, müssen Sie ein scoped array mit der Adresse eines dynamisch reservierten Arrays initialisieren. Die entsprechende Klasse für scoped arrays ist boost::scoped_array und befindet sich in der Headerdatei boost/scoped_array.hpp. #include <boost/scoped_array.hpp> int main() { boost::scoped_array<int> i(new int[2]); *i.get() = 1; i[1] = 2; i.reset(new int[3]); }
  • 4. Die Klasse boost::scoped_array überlädt die beiden Operatoren operator[]() und operator bool(). Über operator[]() können Sie direkt auf eine Position im Array zugreifen - ein Objekt vom Typ boost::scoped_array verhält sich also wie das Array, dessen Adresse es speichert. So wie bei boost::scoped_ptr stehen ebenfalls die beiden Methoden get() und reset() zur Verfügung, mit denen die im boost::scoped_array gespeicherte Adresse abgerufen und neugesetzt werden kann. 2.5 Shared Pointer Dieser smart pointer wird in der Praxis sehr häufig eingesetzt und wurde in der ersten Version des C++ Standards schmerzlich vermisst. Er ist im sogenannten Technical Report 1 in den C++ Standard aufgenommen worden. Wenn Ihre Entwicklungsumgebung den Technical Report 1 unterstützt, können Sie die Klasse std::shared_ptr aus der Headerdatei memory nutzen. In der Boost C++ Bibliothek heißt dieser smart pointer boost::shared_ptr und befindet sich in der Headerdatei boost/shared_ptr.hpp. Der smart pointer boost::shared_ptr ist grundsätzlich boost::scoped_ptr sehr ähnlich. Der entscheidende Unterschied ist, dass boost::shared_ptr nicht exklusiver Eigentümer eines Objekts ist, sondern mit anderen smart pointern vom Typ boost::shared_ptr das Eigentum teilen kann: Das Objekt, dessen Adresse in mehreren shared pointern gespeichert ist, wird erst dann zerstört, wenn der letzte shared pointer zerstört ist. Da boost::shared_ptr das Eigentum teilen kann, können Kopien dieses smart pointers erstellt werden - etwas, was mit boost::scoped_ptr nicht funktioniert. Dies erst ermöglicht, smart pointer in Containern zu speichen - etwas, was auch nicht mit std::auto_ptr möglich ist, da bei jeder Kopie dieses smart pointers der Eigentümer wechselt. #include <boost/shared_ptr.hpp> #include <vector> int main() { std::vector<boost::shared_ptr<int> > v; v.push_back(boost::shared_ptr<int>(new int(1))); v.push_back(boost::shared_ptr<int>(new int(2))); } Dank des smart pointers boost::shared_ptr ist es wie oben gezeigt möglich, Adressen von dynamisch reservierten Objekten sicher in Containern zu speichern. Der Container speichert zwar Kopien und muss eventuell zusätzliche Kopien erstellen, wenn bereits gespeicherte Elemente neu arrangiert werden müssen. Da boost::shared_ptr jedoch das Eigentum an Objekten teilen kann, sind Kopien gleichwertig. Dies ist wie bereits beschrieben bei std::auto_ptr nicht der Fall, weswegen ein std::auto_ptr auch niemals in einem Container gespeichert werden sollte. Die Klasse boost::shared_ptr überlädt ähnlich wie boost::scoped_ptr die Operatoren operator*(), operator->() und operator bool(). Darüberhinaus stehen ebenfalls
  • 5. wieder die Methoden get() und reset() zur Verfügung, um die gespeicherte Adresse abzurufen und eine neue Adresse zu setzen. #include <boost/shared_ptr.hpp> int main() { boost::shared_ptr<int> i1(new int(1)); boost::shared_ptr<int> i2(i1); i1.reset(new int(2)); } Im obigen Beispiel werden zwei shared pointer i1 und i2 verwendet, die anfangs auf das gleiche Objekt vom Typ int zeigen. Während die Variable i1 direkt mit einer Adresse initialiert wird, erhält die Variable i2 über den Copy-Konstruktor die in i1 gespeicherte Adresse. Wenn dann für i1 die Methode reset() aufgerufen wird, wird die in i1 gespeicherte Adresse neu gesetzt. Das bis dato in i1 gespeicherte Objekt wird jedoch nicht zerstört, da es noch in der Variablen i2 gespeichert ist. Der smart pointer boost::shared_ptr zählt also mit, wie viele shared pointer momentan auf ein Objekt verweisen, und zerstört dieses erst dann, wenn der Gültigkeitsbereich des letzten shared pointers endet. Der smart pointer boost::shared_ptr besitzt eine Besonderheit, die es erlaubt anzugeben, wie ein Objekt zerstört werden soll. Dies erfolgt standardmäßig mit delete, kann jedoch wie im folgenden Beispiel zu sehen auch anders geschehen. #include <boost/shared_ptr.hpp> #include <windows.h> int main() { boost::shared_ptr<void> h(OpenProcess(PROCESS_SET_INFORMATION, FALSE, GetCurrentProcessId()), CloseHandle); SetPriorityClass(h.get(), HIGH_PRIORITY_CLASS); } Dem Konstruktor von boost::shared_ptr kann als zweiter Parameter eine Funktion oder ein Funktionsobjekt übergeben werden. Im obigen Beispiel ist dies die Windows API- Funktion CloseHandle(). Endet der Gültigkeitsbereich der Variablen h, wird nicht delete aufgerufen, sondern die als zweiter Parameter angegebene Funktion. Diese muss mit einem einzigen Parameter vom Typ HANDLE aufgerufen werden können, damit der Code kompiliert. Dies ist bei CloseHandle() der Fall. Dieses Beispiel funktioniert genauso wie das zu Beginn dieses Kapitels vorgestellte Programm, das RAII veranschaulicht hat. Anstatt eine eigene Klasse windows_handle zu definieren, wird hier die Besonderheit von boost::shared_ptr ausgenutzt und dem Konstruktor als zweiter Parameter eine Funktion übergeben, die automatisch aufgerufen wird, wenn der Gültigkeitsbereich des shared pointers endet.
  • 6. 2.6 Shared Array Ein shared array funktioniert grundsätzlich wie ein shared pointer. Der entscheidende Unterschied ist, dass der Destruktor eines shared arrays delete[] aufruft. Das wiederum bedeutet, dass ein shared array mit der Adresse eines dynamisch reservierten Arrays initialisiert werden muss - denn nur diese dürfen mit delete[] freigegeben werden. In den Boost C++ Bibliotheken finden Sie die Klasse boost::shared_array in der Headerdatei boost/shared_array.hpp. #include <boost/shared_array.hpp> #include <iostream> int main() { boost::shared_array<int> i1(new int[2]); boost::shared_array<int> i2(i1); i1[0] = 1; std::cout << i2[0] << std::endl; } Ähnlich wie beim shared pointer kann auch bei einem shared array das Eigentum mit anderen shared arrays geteilt werden. So teilen sich im obigen Programm die beiden Variablen i1 und i2 das Eigentum an einem dynamisch reservierten Array. Wenn über operator[]() auf i1 zugegriffen wird, um den Wert 1 im Array zu speichern, kann über i2 auf das gleiche dynamisch reservierte Array zugegriffen werden und der Wert zum Beispiel auf die Standardausgabe ausgegeben werden. So wie alle bisher in diesem Kapitel vorgestellten smart pointer bietet auch boost::shared_array die Methoden get() und reset() an. Außerdem ist ebenfalls der Operator operator bool() überladen. 2.7 Weak Pointer Alle bisher in diesem Kapitel kennengelernten smart pointer können jeweils für sich allein genommen in unterschiedlichen Situationen verwendet werden. Der weak pointer jedoch macht nur im Zusammenspiel mit dem shared pointer Sinn. Er ist in der Headerdatei boost/weak_ptr.hpp als boost::weak_ptr definiert. #include <windows.h> #include <boost/shared_ptr.hpp> #include <boost/weak_ptr.hpp> #include <iostream> DWORD WINAPI reset(LPVOID p) { boost::shared_ptr<int> *sh = static_cast<boost::shared_ptr<int>*>(p); sh->reset(); return 0; } DWORD WINAPI print(LPVOID p) { boost::weak_ptr<int> *w = static_cast<boost::weak_ptr<int>*>(p); boost::shared_ptr<int> sh = w->lock();
  • 7. if (sh) std::cout << *sh << std::endl; return 0; } int main() { boost::shared_ptr<int> sh(new int(99)); boost::weak_ptr<int> w(sh); HANDLE threads[2]; threads[0] = CreateThread(0, 0, reset, &sh, 0, 0); threads[1] = CreateThread(0, 0, print, &w, 0, 0); WaitForMultipleObjects(2, threads, TRUE, INFINITE); } Die Klasse boost::weak_ptr muss immer mit einem boost::shared_ptr initialisiert werden. Einmal initialisiert bietet sie genaugenommen nur eine einzige nützliche Methode an, nämlich lock(). Diese Methode gibt einen boost::shared_ptr zurück, der das Eigentum mit dem shared pointer teilt, mit dem der weak pointer initialisiert wurde. Für den Fall, dass der shared pointer, der bei der Initialisierung angegeben wurde, nicht mehr auf ein Objekt zeigt, ist auch der von lock() zurückgegebene shared pointer auf 0 gesetzt. Der weak pointer bietet sich an, wenn eine Funktion mit einem in einem shared pointer verwalteten Objekt arbeiten soll, die Lebensdauer dieses Objekts jedoch nicht von dieser Funktion abhängen soll. Die Funktion soll also nur dann mit dem Objekt arbeiten, wenn es momentan in shared pointern, die an anderen Stellen im Programm existieren, verankert ist. Werden diese shared pointer resettet, soll das entsprechende Objekt nicht mehr existieren und nicht unnötigerweise durch einen zusätzlichen shared pointer in der entsprechenden Funktion am Leben gehalten werden. Im obigen Beispiel werden in main() zwei Threads erstellt. Dazu wird auf Funktionen zugegriffen, die vom Betriebssystem Windows zur Verfügung gestellt werden. Obiges Beispiel lässt sich daher ausschließlich unter Windows kompilieren und ausführen. Der eine Thread führt die Funktion reset() aus, der ein Zeiger auf einen shared pointer übergeben wird. Der andere Thread besteht aus der Funktion print(), die einen Zeiger auf einen weak pointer erhält. Dieser weak pointer ist in der Funktion main() mit dem shared pointer initialisiert worden. Wenn das Programm gestartet wird, werden die beiden Funktionen reset() und print() gleichzeitig in zwei Threads ausgeführt. Es lässt sich dabei nicht vorhersagen, in welcher Reihenfolge die verschiedenen Anweisungen in den Threads abgearbeitet werden. Das Problem dabei 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 also sein, dass in der Funktion reset() das Objekt zerstört wird, während gerade in der Funktion print() genau auf dieses Objekt zugegriffen wird, um es auszugeben. Der weak pointer löst diese Problematik wie folgt: Beim Aufruf von lock() wird ein shared pointer zurückgeben. Dieser shared pointer zeigt auf ein Objekt, wenn dieses Objekt zum Zeitpunkt des Aufrufes existiert. Andernfalls ist der shared pointer auf 0 gesetzt und somit ein typischer Null-Zeiger.
  • 8. Der weak pointer allein hat demnach keinen Einfluss auf die Lebensdauer eines Objekts. Damit trotzdem in der Funktion print() gefahrlos auf das Objekt zugegriffen kann, ohne dass es plötzlich in einem anderen Thread zerstört wird, gibt lock() einen shared pointer zurück. Selbst wenn nun in einem anderen Thread versucht wird, das Objekt zu zerstören, existiert es dank des von lock() zurückgegebenen shared pointers weiter. 2.8 Intrusive Pointer Der intrusive pointer funktioniert genauso wie der shared pointer. Während boost::shared_ptr jedoch automatisch mitzählt, wie viele shared pointer momentan auf ein Objekt zeigen und das Eigentum teilen, muss beim intrusive pointer der Entwickler mitzählen. Das ist zum Beispiel dann von Vorteil, wenn auf Klassen aus Frameworks zugegriffen wird, die ihrerseits sowieso schon mitzählen. In den Boost C++ Bibliotheken befindet sich der intrusive pointer in der Headerdatei boost/intrusive_ptr.hpp und heißt boost::intrusive_ptr. #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)); CComDispatchDriver dd(disp.get()); CComVariant arg("C:Windows"); CComVariant ret(false); dd.Invoke1(CComBSTR("FolderExists"), &arg, &ret); std::cout << (ret.boolVal != 0) << std::endl; } void main() { CoInitialize(0); check_windows_folder(); CoUninitialize(); } Im obigen Beispiel wird auf COM-Funktionen zugegriffen, so dass der Code ausschließlich unter Windows kompiliert und ausgeführt werden kann. COM-Objekte bieten sich insofern als Beispiel für boost::intrusive_ptr an als dass sie mitzählen, wie viele Zeiger momentan
  • 9. auf sie verweisen. Der interne Zä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 nun innerhalb der Funktionen intrusive_ptr_add_ref() und intrusive_ptr_release() aufgerufen, um den internen Zähler im COM-Objekt zu inkrementieren und dekrementieren. Das COM-Objekt, das in diesem Beispiel 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 den Ordner C:Windows gibt. Wie das 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 des intrusive pointers 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. 2.9 Pointer Container Nachdem Sie nun die verschiedenen smart pointer der Boost C++ Bibliotheken kennengelernt haben, können Sie für dynamisch reservierte Objekte und Arrays sicheren Code schreiben. Häufig müssen Objekte in Containern verwaltet werden, was wie Sie bereits gesehen haben mit boost::shared_ptr und boost::shared_array auch problemlos funktioniert. #include <boost/shared_ptr.hpp> #include <vector> int main() { std::vector<boost::shared_ptr<int> > v; v.push_back(boost::shared_ptr<int>(new int(1))); v.push_back(boost::shared_ptr<int>(new int(2))); } Während der Code im obigen Beispiel völlig korrekt ist und smart pointers so angewandt werden könnten, ist das wegen der wiederholten Angabe von boost::shared_ptr nicht nur mehr Schreibarbeit. Das Kopieren von boost::shared_ptr in, aus und innerhalb von Containern erfordert ein ständiges In- und Dekrementieren von Zählern und ist daher weniger effizient. Die Boost C++ Bibliotheken bieten daher Pointer Container an, die auf die Verwaltung dynamisch reservierter Objekte spezialisiert sind. #include <boost/ptr_container/ptr_vector.hpp> int main() { boost::ptr_vector<int> v; v.push_back(new int(1)); v.push_back(new int(2)); }
  • 10. Die Klasse boost::ptr_vector, die in der Headerdatei boost/ptr_container/ptr_vector.hpp definiert ist, funktioniert genauso wie der im vorherigen Beispiel verwendete Container, der mit dem Template-Parameter boost::shared_ptr instantiiert ist. Dadurch, dass boost::ptr_vector auf dynamisch reservierte Objekte spezialisiert ist, ist diese Klasse effizienter und einfacher anzuwenden. Da boost::ptr_vector jedoch alleiniger Eigentümer aller Objekte ist, kann nicht wie bei std::vector<boost::shared_ptr<int> > das Eigentum zum Beispiel mit einem shared pointer geteilt werden, der nicht im Container gespeichert ist. Neben boost::ptr_vector stehen weitere Container zur Verfügung, die auf die Verwaltung dynamisch reservierter Objekte spezialisiert sind. Dazu gehören boost::ptr_deque, boost::ptr_list, boost::ptr_set, boost::ptr_map, boost::ptr_unordered_set und boost::ptr_unordered_map. Diese Container entsprechen den aus dem C++ Standard bekannten Containern. Die letzten beiden genannten Container entsprechen den Containern std::unordered_set und std::unordered_map, die im Technical Report 1 dem C++ Standard hinzugefügt wurden. Sie sind außerdem als boost::unordered_set und boost::unordered_map in der Boost C++ Bibliothek Unordered definiert, die Sie einsetzen können, wenn die von Ihnen verwendete Implementation des C++ Standards den Technical Report 1 nicht unterstützt. 2.10 Aufgaben Verbessern Sie folgendes Programm, indem Sie einen geeigneten smart pointer verwenden: 1. #include <iostream> 2. #include <cstring> 3. 4. char *get(const char *s) 5. { 6. int size = std::strlen(s); 7. char *text = new char[size + 1]; 8. std::strncpy(text, s, size + 1); 9. return text; 10. } 11. 12. void print(char *text) 13. { 14. std::cout << text << std::endl; 15. } 16. 17. int main(int argc, char *argv[]) 18. { 19. if (argc < 2) 20. { 21. std::cerr << argv[0] << " <data>" << std::endl; 22. return 1; 23. } 24. 25. char *text = get(argv[1]); 26. print(text); 27. delete[] text; 28. } Verbessern Sie folgendes Programm:
  • 11. 29. #include <vector> 30. 31. template <typename T> 32. T *create() 33. { 34. return new T; 35. } 36. 37. int main() 38. { 39. std::vector<int*> v; 40. v.push_back(create<int>()); 41. }