Open Source im professionellen Einsatz
Linux-Magazin 05/2010
1049

Kluge Pointer

Smart Pointer überwachen den Lebenszyklus der Ressource nach dem RAII-Idiom [4]. Ihr C++-Ahne ist der »auto_ptr«, der aber eine große konzeptionelle Schwäche hat: Wird ein »auto_ptr« kopiert, geht seine Ressource in den Besitz des neuen Objekts über. Im Gegensatz hierzu hält der »shared_ptr« eine Referenz auf die Ressource und eine auf den Referenzzähler. Abbildung 2 stellt das Kopieren des »auto_ptr« und des »shared_ptr« gegenüber.

Dieses Verhalten führt einerseits dazu, dass sich »auto_ptr« nicht in Standard-Containern verwenden lässt, und andererseits, dass der Code in Listing 8 undefiniertes Verhalten aufweist, da »auto1« die Ressource »new int(5)« nicht mehr besitzt. Das C++-Standard-Komitee entschloss sich daher, »auto_ptr« als »deprecated« zu kennzeichnen und dafür den neuen Smart Pointer »unique_ptr« zu definieren. Listing 9 zeigt die neuen Smart Pointer im Einsatz, Abbildung 3 die entsprechende Ausgabe dazu.

Zeile 1 definiert einen Vektor über »shared_ptr«. Der »sharedPtr« aus Zeile 2 wird in der nächsten Zeile auf den Vektor geschoben. »shared_ptr« lassen sich im Gegensatz zu den »auto_ptr« von C++ in Standardcontainern verwenden, da sie kopierkonstruierbar und zuweisbar sind. Die Zuweisung »*sharedPtr="modified"« modifiziert auch den Shared Pointer in dem Vektor, wie die Ausgabe zeigt.

Abbildung 3: Die Ausgabe von Listing 9 zeigt die Entwicklung des Referenzzählers beim Einsatz verschiedener Pointer-Typen.

Listing 8: Kopieren eines
»auto_ptr«

01 std::auto_ptr<int> auto1(new int(5));
02 std::auto_ptr<int> auto2(auto1);
03 int a= *auto1;

Listing 9:
»shared_ptr«, »weak_ptr« und
»unique_ptr«

01 std::vector< std::tr1::shared_ptr<std::string> > sharedVec;
02 std::tr1::shared_ptr<std::string> sharedPtr( new std::string("initial"));
03 sharedVec.push_back(std::tr1::shared_ptr<std::string>( sharedPtr));
04 std::cout << "Values:          sharedPtr     sharedVec" << std::endl;
05 std::cout << "Initial:     " << "    " << *sharedPtr << "       " << *sharedVec[0] <<  std::endl;
06 *sharedPtr="modified";
07 std::cout << "Modified:    " << "    " <<  *sharedPtr << "       "  << *sharedVec[0] <<  std::endl;
08 std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
09 {
10   std::shared_ptr<std::string> localSharedPtr( sharedPtr );
11   std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
12 }
13 std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
14 std::weak_ptr<std::string> weakPtr( sharedPtr );
15 std::cout << "use_count: " << sharedPtr.use_count() << std::endl;
16 std::unique_ptr<std::string> uniquePtrFirst( new std::string("only one") );
17 // std::unique_ptr<std::string> uniquePtrSecond( uniquePtrFirst); will not compile
18 std::unique_ptr<std::string> uniquePtrSecond( std::move(uniquePtrFirst));
19 std::cout << "uniquePtrFirst.get(): " << uniquePtrFirst.get() << " *uniquePtrSecond.get(): " << *uniquePtrSecond.get() << std::endl;

Mitgezählt

Die Aufrufe »sharedPtr.use_count()« geben den Referenzzähler der gemeinsam genutzten Ressource aus. Schön ist zu sehen, wie der Referenzzähler nach der Destruktion von »localSharedPtr« am Ende des Blocks in Zeile 13 wieder den Wert 2 annimmt. Der »weak_ptr« in Zeile 14 erhöht den Referenzzähler auf die geteilte Ressource nicht.

Der »uniquePtrFirst« in Zeile 16 besitzt die Ressource exklusiv, sie kann nur durch eine explizite Verwendung von »std::move« an »uniquePtrSecond« in Zeile 18 übertragen werden. Dies ist der Grund, warum »uniquePtrFirst.get()« in Zeile 19 ein Nullpointer ist, »*uniquePtrSecond.get()« hingegen die Ressource besitzt und ausgeben kann.

Die neuen Smart Pointer geben dem C++-Programmierer die notwendigen Hilfsmittel an die Hand, um eine automatische deterministische Speicherbereinigung umzusetzen. Deterministisch, weil der Speicher sofort im Destruktor wieder freigegeben und nicht - wie bei der Garbage-Collector-Implementierung in Java oder Python üblich - lediglich dem Garbage Collector zur künftigen Freigabe überlassen wird.

Die neue Standardbibliothek hat noch mehr zu bieten: Die neuen Container zu Tupeln, Arrays und Hashtabellen runden C++0x weiter ab. C++ besitzt Paare, C++0x bekommt Tupel. Tupel sind eine Verallgemeinerung von Paaren - sie können mindestens zehn verschiedene Elemente binden. Ihre Elemente (Zeilen 1 und 2) in Listing 10 lassen sich in Standardcontainern (Zeile 4) verwenden, miteinander vergleichen (Zeile 5) und besitzen eine feste Dimension. Mit Hilfsfunktionen wie »std::make_tuple« (Zeile 2) lässt sich ein Tupel schnell erzeugen und mit »std::get« lesen (Zeile 6) und schreiben (Zeile 7).

Listing 10: Tupel

01 std::tuple<std::string,int,float> tup1("first tuple",3,4.17);
02 std::tuple<std::string,int,double> tup2= std::make_tuple("second tuple",4,1.1);
03 std::vector < std::tuple<std::string,int,float > > tupVec;
04 tupVec.push_back(tup1);
05 bool a= tup1 < tup2;
06 std::string str= std::get<0>(tup1);
07 std::get<0>(tup2)= "Second Tuple";

Der ebenfalls neue sequenzielle Container »std::array« ist ein Container-Wrapper für Arrays fester Länge, der mit der Standard Template Library (STL) konform geht. Er verbindet das Laufzeitverhalten des C-Array mit den Schnittstellen des C++-Vektors. Die Tabelle 1 stellt die feinen Unterschiede der drei sequenziellen Datentypen dar.


Einer der im C++98-Standard am häufigsten vermissten Container ist die Hashtabelle, auch bekannt als Dictionary oder assoziatives Array. Hashtabellen erlauben den performanten Zugriff auf die Werte in Containern mit dem assoziierten Schlüssel, sie sind aus dem Programmiererleben nicht wegzudenken [10].

C++0x füllt nun endlich diese Lücke: Die Hashtabellen sind unter ihren Namen »unordered_map«, »unordered_set«, »unordered_multimap« und »unordered_multiset« Bestandteil des neuen C++0x-Standards. Die vier Hashtabellen lassen sich gut mit Hilfe dieser beiden Fragen unterscheiden: Kommen die Schlüssel einfach vor? Ist den Schlüsseln ein Wert zugeordnet?

Die sperrigen Namen der Hashtabellen sind zum einen der Tatsache geschuldet, dass die intuitiveren Namen schon für verschiedene C++-Erweiterungen vergeben sind. Zum anderen entsprechen die neuen Container den bekannten C++-Containern »map«, »set«, »multimap« und »multimap«. Die bekannten Container und die neuen wie »unordered_map« bieten zwar ein sehr ähnliches Interface, unterscheiden sich aber in der Performance: Nur die Schlüssel der C++-Container sind geordnet. Dadurch reduziert sich die Zugriffszeit im Idealfall von einem logarithmischen auf ein konstantes Laufzeitverhalten.

Linux-Magazin kaufen

Einzelne Ausgabe
 
Abonnements
 
TABLET & SMARTPHONE APPS
Bald erhältlich
Get it on Google Play

Deutschland

Ähnliche Artikel

  • C++11

    Der Smart Pointer gilt als der klügste seiner Art in C++11. Sein Fachgebiet ist die elegante Garbage Collection, manchmal muss aber sein kleiner Bruder Weak Pointer mithelfen.

  • C++11

    Die neuen Smart Pointer in C++11 machen den Zugriff auf Ressourcen transparent und räumen hinter sich auf. Dieser Artikel stellt als ersten Vertreter den Unique Pointer vor.

  • C++11

    Klein, aber oho: Platziert ein Entwickler drei Punkte ("…") geschickt an der richtigen Stelle im C++-Code, entpacken die so genannten Variadic Templates ihre Argumente an Ort und Stelle.

  • C++11

    2014 ist ein besonderes Jahr für C++. Drei Jahre nach C++11 erfährt der Sprachstandard mit C++14 den letzten Feinschliff. Neben generischen Lambda-Funktionen und der vereinfachten Ermittlung des Rückgabetyps kann C++14 vor allem mit einem Feature punkten: Reader-Writer-Locks.

  • C++11

    Die Referenz-Wrapper bilden eine kleine, aber feine neue Bibliothek in C++11. Diese Objekte verhalten sich wie Referenzen, der Artikel erklärt die Details.

comments powered by Disqus

Stellenmarkt

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.