Es gibt viele Zeiger in C ++, aber um ehrlich zu sein, verwende ich in der C ++ - Programmierung (speziell mit dem Qt Framework) in 5 Jahren nur den alten rohen Zeiger:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
Ich weiß, dass es viele andere "kluge" Hinweise gibt:
// shared pointer:
shared_ptr<SomeKindofObject> Object;
// unique pointer:
unique_ptr<SomeKindofObject> Object;
// weak pointer:
weak_ptr<SomeKindofObject> Object;
Aber ich habe nicht die geringste Ahnung, was ich mit ihnen anfangen soll und was sie mir im Vergleich zu rohen Zeigern bieten können.
Zum Beispiel habe ich diesen Klassenheader:
#ifndef LIBRARY
#define LIBRARY
class LIBRARY
{
public:
// Permanent list that will be updated from time to time where
// each items can be modified everywhere in the code:
QList<ItemThatWillBeUsedEveryWhere*> listOfUselessThings;
private:
// Temporary reader that will read something to put in the list
// and be quickly deleted:
QSettings *_reader;
// A dialog that will show something (just for the sake of example):
QDialog *_dialog;
};
#endif
Dies ist natürlich nicht erschöpfend, aber für jeden dieser drei Zeiger ist es in Ordnung, sie "roh" zu lassen, oder sollte ich etwas passenderes verwenden?
Und wenn ein Arbeitgeber beim zweiten Mal den Kodex liest, wird er dann streng darauf achten, welche Art von Zeigern ich verwende oder nicht?
c++
programming-practices
pointers
qt
smart-pointer
CheshireChild
quelle
quelle
Antworten:
Ein "roher" Zeiger ist nicht verwaltet. Das heißt, die folgende Zeile:
... 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 einfachauto_ptr
, Speicher zu verlieren. Es reicht jedoch für begrenzte Fälle wie diesen aus: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<>
: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:Im Gegensatz zu
auto_ptr<>
,unique_ptr<>
ist hier gut erzogene, und wenn dievector
komprimiert die Größe, keines der Objekte versehentlich während der gelöscht wirdvector
kopiert seine Sicherungsspeicher.shared_ptr
undweak_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 verwendenstd::shared_ptr<>
: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 allershared_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 Zeigerdelete
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ügenNode
:Wenn wir nun a entfernen
Node
, gibt es einen zyklischen Verweis darauf. Es wird niemalsdelete
d sein, weil sein Referenzzähler niemals Null sein wird.Um dieses Problem zu lösen, verwenden Sie ein
std::weak_ptr<>
: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:
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
undmake_unique
Nun gibt es einige kleinere Probleme mit
shared_ptr<>
undunique_ptr<>
die angegangen werden sollten. Die folgenden zwei Zeilen haben ein Problem:Wenn
thrower()
eine Ausnahme ausgelöst wird, verlieren beide Zeilen Speicher. Darüber hinausshared_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 bietetstd::make_unique<>()
, um dieses Problem zu lösen:In beiden Fällen tritt
thrower()
kein Speicherverlust auf , auch wenn eine Ausnahme ausgelöst wird. Als Bonusmake_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 diesdelete
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 diesshared_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.quelle
shared_ptr
ist ein separates Objekt - eine separate Zuordnung - vomnew
ed-Objekt. Sie existieren an verschiedenen Orten.make_shared
hat die Fähigkeit, sie am selben Ort zusammenzufügen, was unter anderem die Cache-Lokalität verbessert.shared_ptr
ist ein Objekt. Und um ein Objekt zu verwalten, muss es ein (Referenzanzahl (schwach + stark) + Zerstörer) -Objekt zuweisen.make_shared
ermöglicht die Zuordnung dieses und des verwalteten Objekts als ein Teil.unique_ptr
verwendet diese nicht, daher gibt es keinen entsprechenden Vorteil, abgesehen davon, dass das Objekt immer im Besitz des Smart-Pointers ist. Als Nebenwirkung kann man hat eine ,shared_ptr
die besitzt ein zugrunde liegendes Objekt und anullptr
, oder die nicht selbst tut und stellt einen nicht-Nullpointer.shared_ptr
tut: 1. Es teilt sich den Besitz eines Objekts (dargestellt durch ein internes dynamisch zugewiesenes Objekt mit einer schwachen und einer starken Referenzanzahl sowie einem Deleter). . 2. Es enthält einen Zeiger. Diese beiden Teile sind unabhängig.make_unique
undmake_shared
beide stellen sicher, dass das zugewiesene Objekt sicher in einem Smart-Pointer abgelegt wird. Darüber hinausmake_shared
ermöglicht das Eigentum-Objekt und den verwalteten Zeiger zusammen zuordnet.