C++ Resourcenmanagement
Header: <boost/shared_ptr.hpp> Namespace: boost bzw. Header: <memory> (gcc: <tr1/memory>) Namespace: std::tr1
Der shared_ptr gehört zu den sog. Smart Pointer. Smart Pointer sind Objekte, die einen Zeiger auf ein dynamisch erzeugtes Objekt halten und sich ihrem Benutzer gegenüber wie dieser Zeiger verhält (sie sind damit ein Beispiel des Proxy Pattern). Der Vorteil von Smart Pointern gegenüber normalen Zeigern ist, dass sie die Kontrolle über die Lebensdauer des Objekts übernehmen und damit die Arbeit mit dynamisch erzeugten Objekten extrem vereinfachen.
Die Besonderheit des shared_ptr gegenüber anderen Smart Pointer (z.B. auto_ptr) ist, dass
mehrere Instanzen des shared_ptr gleichzeigtig auf ein einzelnes Objekt verwiesen können. Um
die Lebensdauer des Objekts zu kontrollieren verwendet er einen Referenzzähler. Jede Kopie inkrementiert
diesen Zähler, jeder Destruktor dekremiert ihn. Ist der Zähler bei Null angekommen, wird das Objekt
gelöscht.
Das folgende Beispiel soll die Funktionsweise des Referenzzählers demonstrieren:
typedef std::tr1::shared_ptr<std::string> string_ptr; void print(const string_ptr text) { std::cout << *text; } void f() { // Referenzzähler R=0 string_ptr s(new std::string("Hello World!")); // R=1 { string_ptr ein_zweiter = s; // R=2 print(ein_zweiter); // R=3 // R=2 } // R=1 } // R=0 -> "Hello World!" wird gelöscht
Neben dem hohen Nutzen des shared_ptr macht das Beispiel auch klar, dass jede Kopie oder Zuweisung gegenüber eines einfachen Zeigers mit zusätzlichen Kosten verbunden ist, nämlich Kosten durch das Inkrementieren oder Dekrementieren des Referenzzählers. Außerdem ist das Anlegen eines neuen Zeigers mit einer zusätzlichen Allokation verbunden, denn der Referenzzähler muss im Free Store angelegt werden. In fast allen Anwendungen werden diese Kosten aber vernachlässigbar klein sein, so dass es zu empfehlen ist zunächst immer auf die Nutzung des shared_ptr zurückzugreifen. Sollte es sich durch den Einsatz eines Profilers herausstellen, dass diese Kosten tatsächlichen Einfluss auf die Performance des Systems haben, kann man sich über effektivere Alternativen (z.B. den boost::scoped_ptr) oder eine eigene, angepasste Implementierung Gedanken machen.
Der shared_ptr ist ein hervorragendes Template, um sich von den Sorgen über Lebensdauer und Speicherverwaltung von dynamisch erzeugten Objekten zu befreien. In einigen Situationen führt ein blindes Vertauen in den klugen Zeiger jedoch zu schwerwiegenden Fehlern, die schwer aufzuspüren sind und viel von unserer kostebaren Zeit kosten können. Es sollten daher einige wichtige Regeln eingehalten werden.
Temporäre shared_ptr als Argument in Funktionen sollten vermieden werden. Das Beispiel aus Herb Sutters GotW Exception-Safe Function Calls für auto_ptr gilt gleichermaßen für den shared_ptr.
void f( shared_ptr<T1>, T2 ); const T2 g(); // Schlecht: f( shared_ptr<T1>( new T1 ), g() );
Der C++ Standard garantiert nur, dass alle Argumente einer Funktion vollständig bestimmt/ausgeführt sind, bevor die Funktion ausgeführt wird. Die Reihenfolge, in der die Argumente abgearbeitet werden ist unbestimmt, so dass eine folgende Abfolge möglich ist.
Wirft der Aufruf von g() eine Ausnahme, entsteht an dieser Stelle ein Speicherleck. T1 ist bereits vor dem Aufruf von g() vollständig erzeugt, der allokierte Speicher wird daher nicht automatsich freigegeben. Da der Konstruktor des shared_ptrs nie aufgerufen wird, sorgt auch dieser nicht für die Freigabe des Speichers. Auch wenn es etwas mehr Schreibarbeit bedeutet, sollte daher auf temporäre shared_ptr als Argumente verzichtet werden.
void f( shared_ptr<T1>, T2 ); const T2 g(); // Gut: shared_ptr<T1> pt( new T1 ); f( pt, g() );
Eine weitere Falle lauert bei der gleichzeitigen Verwendung von "normalen Zeigern" und shared_ptr auf das selber Objekt, wie es z.B. bei der Verwendung von APIs schnell passieren kann.
void API::set(A *); // speichert einen A * A * API::get(); // gibt den gleichen A * zurück shared_ptr<A> p(new A); APIset(p.get()); { shared_ptr<A> pt( APIget() ); pt->f(); } // pt dtor löscht das A p->f(); // fehler
Der shared_ptr entscheidet mit einem Referenzzähler, wann ein Objekt zu löschen ist.
Zuweisungsoperator und Kopierkonstruktor inkrementieren diesen Zähler. Im oberen Beispiel wird vom zweiten
shared_ptr der Zähler nicht inkrementiert; Er "weiß" nichts vom ersten shared_ptr.
Die Folge ist, dass er in seinem Destruktor das Objekt auf das beide shared_ptr zeigen löscht. Der
darauf folgende Zugriff auf das Objekt ist ungültig.
Den gleichen Effekt hat die folgende Vorgehensweise.
{
A * a = new A;
shared_ptr<A> p1(a);
shared_ptr<A> p2(a);
} // Fehler! *a wird zweimal gelöscht
{
A * a = new A;
shared_ptr<A> p1(a);
shared_ptr<A> p2(p1); // <- Kopierkonstruktor
} // gut
{
shared_ptr<A> p1(new A); // Besser, kein Rohzeiger
shared_ptr<A> p2(p1); // <- Kopierkonstruktor
} // gut
Als Regel sollte hier festgehalten werden, Objekte sofort nach deren Erzeugung dem shared_ptr zu übergeben und nur über ihn zu referenzieren, zu kopieren und zuzuweisen. Das mischen von shared_ptr mit rohen Zeigern sollte vermieden werden.
Besondere Aufmerksamkeit ist auch geboten, wenn Objekte shared_ptr auf sich selber speichern können oder zwei Objekte gegenseitig auf sich verweisen. Eine Beispielklasse, die in einem shared_ptr einen Verweis zu einem Freund speichert:
class person { public: explicit person(const string & name) : name_(name) { } void set_friend(shared_ptr<person> f) { friend_ = f; } private: shared_ptr<person> friend_; string name_; };
Betrachetn wir nun folgende Anwendung dieser Klasse.
{
shared_ptr<person> uwe(new person("uwe"));
uwe->set_friend(uwe);
} // was nun ??
Hier wurde ordnungsgemäß durch new ein Uwe erzeugt und einem shared_ptr zugewiesen. Uwe ist ein einsamer Mensch und soll sich selber als Freund bekommen. Nach dem Aufruf der set_friend Methode existieren genau zwei shared_ptr auf Uwe: einer im Gültigkeitsbereich unserer Funktion, einer als Member von Uwe. Wird unsere Funktion nun beendet, wird der erste shared_ptr zerstört. Da dieser weiß, dass noch ein zweiter shared_ptr auf Uwe existiert, löscht er Uwe nicht! Da sich sonst niemand für Uwes Abgang verantwortlich fühlt ensteht hier ein Speicherleck. Wer meint, dass Freundschaft mit sich selbst sowieso unsinnig sei betrachte die folgende zweite Funktion:
{
shared_ptr<person> uwe(new person("uwe"));
shared_ptr<person> erwin(new person("erwin"));
uwe->set_friend(erwin);
erwin->set_friend(uwe);
}
Zu Uwe gesellt sich hier Erwin. Die beiden nutzen die Gelegenheit und gehen eine Freundschaft miteinander ein. Was aber passiert am Ende der Geschichte? Zunächst wir der shared_ptr uwe zerstört. Da noch ein zweiter shared_ptr zu Uwe existiert - nämlich in Erwin - wird Uwe nicht gelöscht. Als nächstes wird shared_ptr erwin zerstört. Da Uwe immer noch lebt und ebenso noch einen shared_ptr auf Erwin speichert, wird auch Erwin hier nicht gelöscht!
Bei beiden Beispiele ist das Problem recht einfach zu erkennen und zu beheben. Betrachten wir aber folgendes Beispiel für eine Klasse, die eine bestimmte Aufgabe bearbeitet und nach Fertigstellung eine Benachrichtigungsfunktion aufruft.
class worker { public: typedef boost::function<void (void)> notify_fun; void notify(notify_fun f) { fun_ = f; } void job() { // do some bogus action fun_(); } private: notify_fun fun_; };
Eine Anwendung dieser Klasse, die durch unvorsichtigen Gebrauch des shared_ptr zu Speicherlecks führt:
typedef shared_ptr<worker> worker_ptr; void f(worker_ptr p); int main() { worker_ptr w(new worker); w->notify(tr1::bind(&f, w)); w->job(); return 0; }
Hier wird dem worker ein Funktionsobjekt übergeben, das auf die Funktion f zeigt. Mittels tr1::bind [2] wird ein shared_ptr<worker> auf den worker gebunden, um diesen beim Aufruf der Funktion identifizieren zu können. Die Folge ist der gleiche Effekt wie bei der person Klasse. worker speichert nun einen shared_ptr auf sich selbst, gebunden in seinem fun_ Member.
Dem Problem der zyklischen Referenzen begnet man z.B. mit dem weak_ptr.
template<class Y> class shared_ptr;
typedef T element_type;
shared_ptr();
| Ausnahmen: | keine |
| Effekte: | Erzeugt einen leeren shared_ptr. |
| Zustand: | use_count() == 0 && get() == 0 |
template<class Y> explicit shared_ptr(Y * p);
| Ausnahmen: | std::bad_alloc |
| Effekte: | Erzeugt einen shared_ptr, der die Lebensdauer von p kontrolliert. Y* muss nach T* konvertierbar und Y muss vollständig deklariert sein. p muss durch new erzeugt worden sein und muss sich per delete löschen lassen. |
| Zustand: | use_count() == 1 && get() == p |
| Hinweis: |
Der Desktruktor ruft delete von Y auf, auch wenn der Destruktor von T nicht virtuell ist.
Der shared_ptr "erinnert" sich an den Originaltyp von p. Damit verhält sich dieser Zeiger anders
als ein Rohzeiger, wie das folgende Beispiel zeigt.
struct base{ ~base(){std::cout << "base dtor\n";} // nicht virtuell void hello() {std::cout << "base hello\n";} // nicht virtuell virtual void vhello() {std::cout << "base hello\n";} // virtuell }; struct derived : public base{ ~derived(){std::cout << "derived dtor\n";} void hello() {std::cout << "derived hello\n";} void vhello() {std::cout << "derived hello\n";} }; int main() { { tr1::shared_ptr<base> p(new derived); p->hello(); // base hello p->vhello(); // derived hello } // derived dtor, base dtor base* p = new derived; p->hello(); // base hello p->vhello(); // derived hello delete p; // base dtor return 0; } |
template<class Y, class D> shared_ptr(Y * p, D d); template<class Y, class D, class A> shared_ptr(Y * p, D d, A a);
| Ausnahmen: | std::bad_alloc oder benutzerdefiniert |
| Effekte: |
Erzeugt einen shared_ptr, der die Lebensdauer von p kontrolliert.
Y* muss nach T* konvertierbar und Y muss vollständig deklariert sein.
D muss kopierbar sein und wird im Destruktor als "deleter" für
p benutzt in der Form d(p); Um die Garantien von Ausnahmen des shared_ptr zu bewahren, darf der Kopierkonstruktor und Destruktor von D keine Ausnahmen werfen. Der zweite Konstruktor verwendet für interne Speicheranforderungen den Allokator A. |
| Zustand: | use_count() == 1 && get() == p |
| Beispiel: |
// Api.h typedef void* Bogus; Bogus CreateBogus(); void DestroyBogus(Bogus p); // main.cpp typedef tr1::shared_ptr<void> bogus_ptr; bogus_ptr create_bogus() { return bogus_ptr(CreateBogus(), &DestroyBogus); } int main(){ bogus_ptr b(create_bogus()); return 0; } |
shared_ptr(const shared_ptr& other); template<class Y> shared_ptr(const shared_ptr<Y>& other);
| Ausnahmen: | keine |
| Effekte: | Erzeugt einen leeren shared_ptr, falls other leer ist oder einen shared_ptr, der gemeinsam mit other die Kontrolle über other.get() übernimmt (shared ownership). Für den zweiten Konstruktor muss Y* nach T* konvertierbar sein. |
| Zustand: | use_count() == other.use_count() && get() == other.get() |
shared_ptr(const weak_ptr& other);
| Ausnahmen: | bad_weak_ptr wenn other.use_count() == 0 |
| Effekte: | Erzeugt einen shared_ptr, der eine Kopie des Zeigers von other speichert und Kontrolle über das Objekt übernimmt. Y* muss nach T* konvertierbar sein. |
| Zustand: | use_count() == other.use_count() |
shared_ptr(auto_ptr& other);
| Ausnahmen: | std::bad_alloc oder benutzerdefiniert |
| Effekte: | Erzeugt einen shared_ptr, der other.release() speichert und Kontrolle über das Objekt übernimmt. Y* muss nach T* konvertierbar sein. |
| Zustand: | use_count() == 1 && other.get() == 0 |
~shared_ptr();
| Ausnahmen: | keine |
| Effekte: |
Kein "side effect", wenn use_count == 0 || use_count > 1 Sonst wird das Objekt per delete p; bzw. d(p); gelöscht. |
shared_ptr& operator=(const shared_ptr& other); template<class Y> shared_ptr& operator=(const shared_ptr<Y>& other); template<class Y> shared_ptr& operator=(auto_ptr<Y>& other);
| Ausnahmen: | Äquivalent zu den Kopierkonstruktoren. 1.&2.: keine / 3.:std::bad_alloc oder benutzerdefinierte |
| Effekte: | Wie die Kopierkonstruktoren, d.h. äquivalent zu shared_ptr(r).swap(*this). |
void swap(shared_ptr& other);
| Ausnahmen: | keine |
| Effekte: | Tauscht den Inhalt von other und *this. |
| Beispiel: |
class pimpled{ struct impl; shared_ptr<impl> pimpl_; public: pimpled(); pimpled& operator=(pimpled other){ pimpl_.swap(other.pimpl_); return *this; } }; |
void reset();
| Effekte: | Äquivalent zu shared_ptr().swap(*this) |
template<class Y> void reset(Y * p);
| Effekte: | Äquivalent zu shared_ptr(p).swap(*this) |
| Beispiel: |
shared_ptr<string> sp(new string("Hello World!")); sp.reset(new string("Hallo Welt!")); |
template<class Y, class D> void reset(Y * p, D d);
| Effekte: | Äquivalent zu shared_ptr(p, d).swap(*this) |
T * get() const;
| Ausnahmen: | keine |
| Effekte: | Gibt den gespeicherten Zeiger zurück. 0 wenn shared_ptr leer ist. |
| Beispiel: |
shared_ptr<string> sp(new string("Hello World!")); sp.get()->empty() == false; |
T& operator*() const;
| Ausnahmen: | keine |
| Effekte: | Gibt eine Referenz auf das Objekt zurück, auf das der gespeicherte Zeiger verweist. Der gespeicherte Zeiger darf nicht 0 sein. |
| Beispiel: |
shared_ptr<string> sp(new string("Hello World!")); cout << *sp; |
T * operator->() const;
| Ausnahmen: | keine |
| Effekte: | Gibt den gespeicherten Zeiger zurück. Der gespeicherte Zeiger darf nicht 0 sein. |
| Beispiel: |
shared_ptr<string> sp(new string("Hello World!")); sp->empty() == false; |
long use_count() const;
| Ausnahmen: | keine |
| Effekte: | Gibt die Gesamtanzahl der shared_ptr Objekte inklusive *this zurück, die auf den selben Zeiger verweisen oder 0, wenn der shared_ptr leer ist. |
| Hinweis: | unique(); ist aus Performancegründen use_count() == 1; vorzuziehen. |
| Beispiel: |
shared_ptr<int> sp1(new int(1)); shared_ptr<int> sp2(sp1); sp1.use_count == 2; |
bool unique() const;
| Ausnahmen: | keine |
| Effekte: | Gibt use_count() == 1 zurück. |
| Hinweis: | Schneller als use_count() == 1. Bei leerem shared_ptr undefinierter Wert. |
| Beispiel: |
shared_ptr<double> sp1(new double(0.1)); sp1.unique() == true; shared_ptr<double> sp2(sp1); sp1.unique() == false; |
operator boolean_type() const;
| Ausnahmen: | keine |
| Effekte: | Gibt einen Ausdruck äquivalent zu get() != 0 zurück. |
| Beispiel: |
shared_ptr<A> sp(createA());
if (sp)
sp->Afunc();
|
Sende ein Kommentar, Frage, Korrekturen, Beschimpfungen...
15.05.2011 Vertexwahn :
"Da der Konstruktor des shared_ptrs nie aufgerufen wird, sorgt auch dieser nicht für die Freigabe des Speichers."
du meinst doch Destruktor?
Nein, es ist schon richtig so. Es wird nichtmal der Konstruktor aufgerufen, da vorger eine Ausnahme auftritt. D.h, dass gar keine shared_ptr Instanz existiert, die den Speicher freigeben könnte.
doxapp c++ Zur Übersicht home