Vor kurzem habe ich die Meinung, dass ich Zeiger Zeiger nicht verwenden sollte. Ich wollte wissen - warum kann ich nicht?
Zum Beispiel, wenn ich eine Klasse foo
habe, ist es möglich, dies zu tun:
Ich habe schon einige Leute gesehen, die solche Praktiken abstimmen, warum ist das so?
Das Speichern von einfachen Zeigern in einem Container kann zu Speicherlecks und ungeordneten Zeigern führen. Das Speichern eines Zeigers in einem Container definiert keinerlei Besitz des Zeigers. Daher kennt der Container die Semantik von Zerstörungs- und Kopiervorgängen nicht. Wenn die Elemente aus dem Behälter entfernt werden, weiß der Behälter nicht, wie er sie richtig zerstören kann, wenn ein Kopiervorgang ausgeführt wird, sind keine Besitz-Semantiken bekannt. Natürlich können Sie diese Dinge immer selbst handhaben, aber dann ist immer noch eine menschliche Fehlermöglichkeit möglich.
Die Verwendung intelligenter Zeiger lässt die Semantik von Besitz und Zerstörung ihnen überlassen.
Zu erwähnen ist noch, dass Container in nicht aufdringlich und aufdringlich unterteilt sind contaiers - sie speichern das tatsächlich bereitgestellte Objekt anstelle einer Kopie, so dass es tatsächlich zu einer Sammlung von Zeigern kommt. Non intrusive Pointer haben einige Vorteile, so dass Sie nicht verallgemeinern können, dass Pointer in einem Container zu jeder Zeit vermieden werden sollten. In den meisten Fällen wird dies empfohlen.
Die Verwendung eines Vektors von rohen Zeigern ist nicht notwendig, wenn Sie sich erinnern, dass die Zeiger keine Besitz-Semantik haben. Wenn Sie new
und delete
verwenden, bedeutet das normalerweise, dass Sie etwas falsch machen.
Die einzigen Fälle, in denen Sie new
oder delete
im modernen C ++ - Code verwenden sollten, sind insbesondere, wenn Sie unique_ptrs konstruieren oder shared_ptrs mit benutzerdefinierten Deletern konstruieren.
Angenommen, wir haben eine Klasse, die ein bidirektionales Graph
implementiert, ein Graph
enthält eine gewisse Menge Vertexes
.
Beachten Sie, wie ich einen Vektor von roher Vertex
* in dieser Vertex
Klasse habe? Ich kann das tun, weil die Lebensdauer des Vertexes
, auf das es verweist, von der Klasse Graph
verwaltet wird. Das Eigentum an meiner Vertex
-Klasse ist explizit, wenn ich nur auf den Code schaue.
Eine andere Antwort schlägt die Verwendung von shared_ptr vor. Ich persönlich mag diesen Ansatz nicht, weil geteilte Zeiger es im Allgemeinen sehr schwer machen, über die Lebensdauer von Objekten nachzudenken. In diesem speziellen Beispiel hätten gemeinsame Zeiger aufgrund der zirkulären Referenzen zwischen Vertexes
nicht funktioniert.
Da der Destruktor des Vektors delete
nicht auf den Zeigern aufruft, ist es leicht, versehentlich Speicher zu verlieren. Der Destruktor eines Vektors ruft die Destruktoren aller Elemente im Vektor auf, aber rohe Pointer haben keine Destruktoren.
Sie können jedoch einen Vektor von intelligenten Zeigern verwenden, um sicherzustellen, dass das Zerstören des Vektors die darin enthaltenen Objekte freigibt. vector<unique_ptr<foo>>
kann in C ++ 11 verwendet werden, und in C ++ 98 mit TR1 können Sie vector<tr1::shared_ptr<foo>>
verwenden (obwohl shared_ptr
einen leichten Overhead im Vergleich zu einem rohen Pointer oder unique_ptr
hat).
Boost hat auch eine Pointer-Container-Bibliothek , in der das spezielle Lösch-auf-Zerstörung-Verhalten ist in den Container selbst eingebaut, so dass Sie keine intelligenten Zeiger benötigen.
Eines der Probleme ist Ausnahmesicherheit .
Angenommen, irgendwo wird eine Ausnahme ausgelöst: In diesem Fall wird der Destruktor von std::vector
aufgerufen. Aber dieser Destruktoraufruf löscht nicht die in dem Vektor gespeicherten Raw-Owning-Zeiger . Die Ressourcen, die von diesen Zeigern verwaltet werden, sind daher leaked (dies können sowohl Speicherressourcen sein, also Speicherlecks haben, als auch Ressourcen ohne Speicher, z. B. Sockets, OpenGL-Texturen usw. ).
Wenn Sie stattdessen einen Vektor von intelligenten Zeigern haben (z. B. std::vector<std::unique_ptr<Foo>>
), dann wird, wenn der Destruktor des Vektors aufgerufen wird, jedes spitze Element (sicher im Besitz eines intelligenten Zeigers) im Vektor ordnungsgemäß gelöscht, seinen Destruktor aufrufen. So werden die Ressourcen, die mit jedem Element verknüpft sind ("intelligent" im Vektor gezeigt), ordnungsgemäß freigegeben.
Beachten Sie, dass Vektoren von beobachtenden Rohzeigern in Ordnung sind (unter der Annahme, dass die Lebensdauer der beobachteten Elemente die des Vektors übersteigt). Das Problem ist mit rohen besitzenden Zeigern.
Weil es viel bessere Alternativen gibt. Speziell:
%Vor%und
%Vor%Der Unterschied besteht darin, dass die obigen Versionen dem Benutzer mitteilen, wie die Lebensdauer der Objekte sichergestellt wird. Die Verwendung von rohen Zeigern führt oft dazu, dass die Zeigern mehr oder weniger als einmal gelöscht werden, insbesondere wenn der Code im Laufe der Zeit geändert wird.
Wenn Sie rohe Zeiger verwenden, müssen Sie deutlich dokumentieren, wie Sie mit der Lebensdauerverwaltung der Objekte umgehen, und hoffen, dass die richtigen Personen die Dokumentation zur richtigen Zeit lesen. Wenn Sie eine Schnittstelle wie shared_ptr oder unique_ptr verwenden, dokumentiert dies die Lebensdauerverwaltung selbst.
Der Vorteil roher Zeiger besteht darin, dass Sie mehr Flexibilität bei der Pflege der Lebensdauerverwaltung haben. Diese zusätzliche Flexibilität ist sehr, sehr selten notwendig.
Es gibt absolut kein Problem bei der Verwendung eines Vektors von Zeigern. Die meisten hier schlagen intelligente Zeiger vor, aber ich muss nur sagen, es gibt kein Problem mit der Verwendung eines Vektors von Zeigern ohne intelligente Zeiger. Ich mache es die ganze Zeit.
Ich stimme mit juanchopanza überein, dass das Problem Ihr Beispiel ist, da die Zeiger von new foo () kommen. In einem normalen, vollständig gültigen Anwendungsfall haben Sie möglicherweise die Objekte in einer anderen Sammlung C, sodass die Objekte automatisch zerstört werden, wenn C zerstört wird. Wenn Sie dann die Objekte in C gründlich bearbeiten, können Sie eine beliebige Anzahl anderer Auflistungen erstellen, die Zeiger auf die Objekte in C enthalten. (Wenn die anderen Auflistungen Objektkopien verwenden würden, wäre das Zeit- und Speicherverschwendung , während Sammlungen von Referenzen ausdrücklich verboten sind.) In diesem Anwendungsfall wollen wir niemals Objekte zerstören, wenn eine Sammlung von Zeigern zerstört wird.