Ein "roher" Zeiger ist nicht verwaltet. Das heißt, die folgende Zeile:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... führt zu einem Speicherverlust, wenn eine Begleitung delete
nicht zum richtigen Zeitpunkt ausgeführt wird.
auto_ptr
Um diese Fälle zu minimieren, std::auto_ptr<>
wurde eingeführt. Aufgrund der Einschränkungen von C ++ vor dem Standard von 2011 ist es jedoch immer noch sehr einfach auto_ptr
, Speicher zu verlieren. Es reicht jedoch für begrenzte Fälle wie diesen aus:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
Einer der schwächsten Anwendungsfälle liegt in Behältern. Dies liegt daran auto_ptr<>
, dass der Container möglicherweise den Zeiger löscht und Daten verliert, wenn eine Kopie von erstellt wird und die alte Kopie nicht sorgfältig zurückgesetzt wird.
unique_ptr
Als Ersatz führte C ++ 11 ein std::unique_ptr<>
:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
Solch ein unique_ptr<>
wird korrekt bereinigt, auch wenn es zwischen Funktionen übergeben wird. Dies geschieht durch semantische Darstellung von "Besitz" des Zeigers - der "Besitzer" räumt auf. Dies macht es ideal für den Einsatz in Behältern:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
Im Gegensatz zu auto_ptr<>
, unique_ptr<>
ist hier gut erzogene, und wenn die vector
komprimiert die Größe, keines der Objekte versehentlich während der gelöscht wird vector
kopiert seine Sicherungsspeicher.
shared_ptr
und weak_ptr
unique_ptr<>
Dies ist zwar nützlich, aber es gibt Fälle, in denen Sie möchten, dass zwei Teile Ihrer Codebasis auf dasselbe Objekt verweisen und den Zeiger herum kopieren können, wobei dennoch eine ordnungsgemäße Bereinigung gewährleistet ist. Ein Baum könnte beispielsweise so aussehen, wenn Sie Folgendes verwenden std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
In diesem Fall können wir sogar an mehreren Kopien eines Stammknotens festhalten, und der Baum wird ordnungsgemäß bereinigt, wenn alle Kopien des Stammknotens zerstört sind.
Dies funktioniert, weil jeder shared_ptr<>
nicht nur den Zeiger auf das Objekt festhält, sondern auch eine Referenzanzahl aller shared_ptr<>
Objekte, die auf denselben Zeiger verweisen. Wenn ein neuer erstellt wird, steigt die Anzahl. Wenn einer zerstört wird, sinkt die Zählung. Wenn die Zählung Null erreicht, ist der Zeiger delete
d.
Dies führt also zu einem Problem: Doppelte verknüpfte Strukturen führen zu Zirkelverweisen. Angenommen, wir möchten parent
unserem Baum einen Zeiger hinzufügen Node
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Wenn wir nun a entfernen Node
, gibt es einen zyklischen Verweis darauf. Es wird niemals delete
d sein, weil sein Referenzzähler niemals Null sein wird.
Um dieses Problem zu lösen, verwenden Sie ein std::weak_ptr<>
:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Jetzt funktionieren die Dinge korrekt und das Entfernen eines Knotens hinterlässt keine starren Verweise auf den übergeordneten Knoten. Es macht das Laufen etwas komplizierter:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
Auf diese Weise können Sie einen Verweis auf den Knoten sperren, und Sie haben eine angemessene Garantie dafür, dass er nicht verschwindet, während Sie daran arbeiten, da Sie an einem shared_ptr<>
davon festhalten.
make_shared
und make_unique
Nun gibt es einige kleinere Probleme mit shared_ptr<>
und unique_ptr<>
die angegangen werden sollten. Die folgenden zwei Zeilen haben ein Problem:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Wenn thrower()
eine Ausnahme ausgelöst wird, verlieren beide Zeilen Speicher. Darüber hinaus shared_ptr<>
ist der Referenzzähler weit vom Objekt entfernt, auf das er zeigt, und dies kann eine zweite Zuordnung bedeuten. Das ist normalerweise nicht wünschenswert.
C ++ 11 bietet std::make_shared<>()
und C ++ 14 bietet std::make_unique<>()
, um dieses Problem zu lösen:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
In beiden Fällen tritt thrower()
kein Speicherverlust auf , auch wenn eine Ausnahme ausgelöst wird. Als Bonus make_shared<>()
haben Sie die Möglichkeit, den Referenzzähler im selben Speicherbereich wie das verwaltete Objekt zu erstellen. Dies kann schneller sein und ein paar Bytes Speicher einsparen, während Sie gleichzeitig eine Ausnahmesicherheitsgarantie erhalten!
Anmerkungen zu Qt
Es ist jedoch zu beachten, dass Qt, das Compiler vor C ++ 11 unterstützen muss, ein eigenes Garbage-Collection-Modell hat: Viele QObject
s verfügen über einen Mechanismus, mit dem sie ordnungsgemäß zerstört werden, ohne dass der Benutzer dies delete
tun muss.
Ich weiß nicht, wie sich QObject
s verhalten wird, wenn es von verwalteten C ++ 11-Zeigern verwaltet wird, daher kann ich nicht sagen, dass dies shared_ptr<QDialog>
eine gute Idee ist. Ich habe nicht genug Erfahrung mit Qt, um es sicher zu sagen, aber ich glaube, dass Qt5 für diesen Anwendungsfall angepasst wurde.