Mittwoch, 12. Dezember 2007

Objekte als Ressourcenverwaltung

Problem
Oftmals wird der Sprache C++ angekreidet, dass sie keine automatische Ressourcenverwaltung besässe. In der Tat ist es nicht einfach Ressourcenlecks zu vermeiden. Es gibt aber Techniken, welche diesem Problem entgegen wirken wie zum Beispiel das Verfahren „Ressourcenerwerb ist Initialisierung“ (RAII – Resource Acquisition Is Initialization).


Lösung
Die angeforderte Ressource wird direkt nach ihrer Initialisierung dem Objekt übergeben, welches diese verwaltet. Und sehr wichtig dabei ist die Freigabe der Ressource wenn sie nicht mehr benötigt wird. Ich spreche hier intelligente Zeiger mit Referenzzählung (RCSP – Reference-Counting Smart Pointer) an.


Prinzip
Im ersten Teil dieses Posts werde ich das Prinzip solcher Pointers erläutern und anhand eines Beispieles zeigen wie diese aufgebaut sind. Danach werde ich ein praktisches Beispiel listen anhand der Klasse shared_ptr der Boost-Bibliothek.


Objektorientierung
Die Objektorientierung macht es möglich: der Destruktor jedes Objektes wird spätestens dann aufgerufen, wenn dessen Variable ihren Gültigkeitsbereich verlässt.


Einfacher Fall
Eine angeforderte und initialisierte Ressource wird nun also einem intelligenten Pointer zur Verwaltung übergeben. Der Zugriff auf die Ressource passiert also nur noch über den Pointer. Nun muss dafür gesorgt werden, dass im Destruktor des Pointers dieselbe Ressource wieder frei gegeben wird.


Erweiterter Fall
Wird die Ressource nun über mehrere intelligente Pointer erreichbar, darf sie erst freigegeben werden wenn keiner dieser Pointer mehr auf sie zeigt. Hierfür wird eine Referenzzählung im intelligenten Zeiger benötigt, was nicht weiter schwierig ist zu implementieren.


Grenzen
So wird einiges einfacher doch im Gegensatz zur Garbage Collection ist es so nicht möglich Kreisreferenzen zu brechen.



Beispiel 1
Folgendes Listing zeigt das Prinzip eines solchen smart pointer. Die Ressource ist in diesem Fall Speicher für ein Objekt der Klasse Person. Die Mainfunktion zeigt diverse Fälle wie solch ein Pointer reagiert. Für das Verständnis wichtig ist ebenfalls die Ausgabe dieses Programms.


Achtung:
Verwenden sie bitte nicht diese Implementierung eines smart pointer. Dieses Programm habe ich nur zur Veranschaulichung geschrieben. Es besitzt noch diverse Fehlerquellen. Lassen sie sich vom darauf folgenden Beispiel inspirieren, wenn sie ebenfalls smart pointer in ihre Programme einbauen möchten.


    1 #include <cstdlib>
2 #include <iostream>
3 #include <string>
4
5 using namespace std;
6
7 /****************************************************
8 * Dummy-Objekt Person
9 ****************************************************/
10 class Person
11 {
12 public:
13 Person(string name) : name(name){}
14
15 string getString()const {return name;}
16 void setName(string str) { name = str; }
17
18 private:
19 string name;
20 };
21
22 /****************************************************
23 * RAII-Objekt SmartPtr
24 *
25 * Wichtige Funktionen von SmartPtr
26 * - SmartPtr()
27 * - SmartPtr(T *t)
28 * - SmartPtr& operator=(SmartPtr const& r)
29 * - T* operator->()const
30 * - ~SmartPtr()
31 ****************************************************/
32 template
33 class SmartPtr
34 {
35 public:
36 // Konstruktor (immer vor Verwenung der Attribute initialisieren)
37 SmartPtr() : type(0), references(0){}; // nur zu Testzwecken
38 SmartPtr(T *t) : type(t), references(new int(1)) {};
39
40 // Kopierzuweisungskonstruktor (sollte immer *this zurückgeben)
41 SmartPtr& operator=(SmartPtr const& r)
42 {
43 // Selbstzuweisung handeln
44 if(this == &r) return *this;
45
46 // mit swap wäre es eleganter gelösst
47 type = r.type;
48 references = r.references;
49
50 *references = *references + 1;
51 return *this;
52 }
53
54 // Destruktor
55 // Zerstört das Objekt erst wenn keine Referenz mehr darauf zeigt!!
56 ~SmartPtr()
57 {
58 *references = *references - 1;
59
60 if(*references == 0){
61 delete type;
62 delete references;
63 cout << "Object destroyed..." << endl;
64 }
65 }
66
67 // "Dereferenzierung" Zugriff auf das Objekt.
68 T* operator->()const
69 {
70 return type;
71 }
72
73 void what()
74 {
75 cout << "Mein Name ist: " << type->getString() << endl;
76 cout << " Referenzen: " << *references << endl;
77 }
78 private:
79 T* type;
80 int* references;
81
82 // Kopierkonstruktor ist privat um diese Möglichkeit auszuschliessen.
83 SmartPtr(SmartPtr const& r);
84 };
85
86 /****************************************************
87 * Testfunktion
88 *
89 * - SmartPtr - Test
90 * - Normale Zuweisung
91 * - Referenzzählung
92 * - Selbstzuweisung
93 * - Objektzerstörung
94 ****************************************************/
95 int main(int argc, char *argv[])
96 {
97 {
98 // Neue Person erstellen und in einem SmartPtr festhalten.
99 cout << endl << "Neue Person." << endl << endl;
100 SmartPtr p1(new Person("Hans Muster"));
101 cout << "p1. "; p1.what(); cout << endl;
102 cout << endl;
103
104 {
105 // Weiterer SmartPtr deklarieren
106 SmartPtr p2;
107 // Zuweisung => gemeinsame Nutzung von Person
108 p2 = p1;
109 // Name der Person ändern über p2
110 p2->setName("Muster Hans");
111
112 cout << "Geaenderte Person ueber p2" << endl << endl;
113 cout << "p1. "; p1.what(); cout << endl;
114 cout << "p2. "; p2.what(); cout << endl;
115 }
116
117 cout << endl;
118 cout << "p2 ist ausserhalb des Sichtbarkeitsbereiches." << endl << endl;
119 cout << "p1. "; p1.what(); cout << endl << endl;
120
121 p1 = p1;
122 cout << "Selbstzuweisung von p1." << endl << endl;
123 cout << "p1. "; p1.what(); cout << endl;
124 }
125
126 system("PAUSE");
127 return EXIT_SUCCESS;
128 }




Beispiel 2
Natürlich gibt es für intelligente Pointer bereits bewährte Bibliotheken. Ich empfehle den shared_ptr von Boost. Im folgenden Listing sehen sie wie dieser zu handhaben ist, wie zuvor mit der entsprechenden Ausgabe.

    1 #include <cstdlib>
2 #include <iostream>
3 #include <string>
4 #include "shared_ptr.hpp"
5
6 using namespace std;
7 using namespace boost;
8
9 class Person
10 {
11 public:
12 Person(string name) : name(name){}
13
14 string getName()const {return name;}
15 void setName(string str) {name = str;}
16 private:
17 string name;
18 };
19
20 void what(string name, long refs);
21
22 int main(int argc, char *argv[])
23 {
24 // Neue Person erstellen und p1 übergeben
25 cout << endl << "Neue Person." << endl << endl;
26 shared_ptr p1(new Person("Hans Muster"));
27 cout << "p1."; what(p1->getName(), p1.use_count());
28 cout << endl;
29 {
30 // p2 initialisieren mit p1 (Kopierkonstruktor)
31 shared_ptr p2(p1);
32 p2->setName("Muster Hans");
33 cout << "Geaenderte Person ueber p2" << endl << endl;
34 cout << "p1."; what(p1->getName(), p1.use_count());
35 cout << "p2."; what(p2->getName(), p2.use_count());
36 }
37 cout << endl;
38 cout << "p2 ist ausserhalb des Sichtbarkeitsbereiches." << endl << endl;
39
40 p1 = p1;
41 cout << "Selbstzuweisung von p1." << endl << endl;
42 cout << "p1."; what(p1->getName(), p1.use_count());
43
44 system("PAUSE");
45 return EXIT_SUCCESS;
46 }
47
48 void what(string name, long refs)
49 {
50 cout << " Mein Name ist: " << name << endl;
51 cout << " Referenzen: " << refs << endl;
52 }



Keine Kommentare: