Der Vorteil der Verwendung std::unique_ptr<T>
(abgesehen davon, dass Sie nicht daran denken müssen, aufzurufen delete
oder delete[]
explizit) ist, dass sie garantiert, dass ein Zeiger entweder nullptr
auf eine gültige Instanz des (Basis-) Objekts verweist. Ich werde darauf zurückkommen , nachdem ich Ihre Frage beantworten, aber die erste Nachricht ist DO intelligente Zeiger verwenden , um die Lebensdauer von dynamisch zugewiesenen Objekten zu verwalten.
Ihr Problem ist nun, wie Sie dies mit Ihrem alten Code verwenden können .
Mein Vorschlag ist, dass Sie immer Verweise auf das Objekt übergeben sollten, wenn Sie das Eigentum nicht übertragen oder teilen möchten . Deklarieren Sie Ihre Funktion folgendermaßen (je const
nach Bedarf mit oder ohne Qualifikationsmerkmale):
bool func(BaseClass& ref, int other_arg) { ... }
Dann wird der Anrufer, der ein hat std::shared_ptr<BaseClass> ptr
, entweder den nullptr
Fall behandeln oder ihn bitten bool func(...)
, das Ergebnis zu berechnen:
if (ptr) {
result = func(*ptr, some_int);
} else {
/* the object was, for some reason, either not created or destroyed */
}
Dies bedeutet, dass jeder Aufrufer versprechen muss, dass die Referenz gültig ist und dass sie während der Ausführung des Funktionskörpers weiterhin gültig ist.
Hier ist der Grund, warum ich fest davon überzeugt bin, dass Sie keine Rohzeiger oder Verweise auf intelligente Zeiger übergeben sollten.
Ein Rohzeiger ist nur eine Speicheradresse. Kann eine von (mindestens) 4 Bedeutungen haben:
- Die Adresse eines Speicherblocks, in dem sich Ihr gewünschtes Objekt befindet. ( das Gute )
- Die Adresse 0x0, von der Sie sicher sein können, ist nicht dereferenzierbar und hat möglicherweise die Semantik "nichts" oder "kein Objekt". ( das schlechte )
- Die Adresse eines Speicherblocks, der sich außerhalb des adressierbaren Bereichs Ihres Prozesses befindet (eine Dereferenzierung führt hoffentlich zum Absturz Ihres Programms). ( der Hässliche )
- Die Adresse eines Speicherblocks, der dereferenziert werden kann, aber nicht das enthält, was Sie erwarten. Möglicherweise wurde der Zeiger versehentlich geändert und zeigt jetzt auf eine andere beschreibbare Adresse (einer völlig anderen Variablen in Ihrem Prozess). Das Schreiben in diesen Speicherort macht manchmal während der Ausführung viel Spaß, da sich das Betriebssystem nicht beschwert, solange Sie dort schreiben dürfen. ( Zoinks! )
Die korrekte Verwendung intelligenter Zeiger verringert die eher beängstigenden Fälle 3 und 4, die normalerweise beim Kompilieren nicht erkennbar sind und die Sie im Allgemeinen nur zur Laufzeit erleben, wenn Ihr Programm abstürzt oder unerwartete Dinge ausführt.
Das Übergeben von intelligenten Zeigern als Argumente hat zwei Nachteile: Sie können die const
-ness des spitzen Objekts nicht ändern, ohne eine Kopie zu erstellen (was den Overhead erhöht shared_ptr
und nicht möglich ist unique_ptr
), und Sie haben immer noch die zweite ( nullptr
) Bedeutung.
Ich habe den zweiten Fall aus gestalterischer Sicht als ( den schlechten ) markiert . Dies ist ein subtileres Argument für Verantwortung.
Stellen Sie sich vor, was es bedeutet, wenn eine Funktion a nullptr
als Parameter empfängt . Zuerst muss entschieden werden, was damit zu tun ist: Verwenden Sie einen "magischen" Wert anstelle des fehlenden Objekts? Verhalten komplett ändern und etwas anderes berechnen (für das das Objekt nicht erforderlich ist)? Panik und eine Ausnahme werfen? Was passiert außerdem, wenn die Funktion 2 oder 3 oder noch mehr Argumente per Rohzeiger akzeptiert? Es muss jeden von ihnen überprüfen und sein Verhalten entsprechend anpassen. Dies fügt der Eingabevalidierung ohne wirklichen Grund eine völlig neue Ebene hinzu.
Der Anrufer sollte derjenige sein, der über genügend Kontextinformationen verfügt, um diese Entscheidungen zu treffen. Mit anderen Worten, je schlechter Sie wissen, desto weniger erschreckend ist das Schlechte . Die Funktion sollte andererseits nur das Versprechen des Anrufers erfüllen, dass der Speicher, auf den er zeigt, sicher ist, wie beabsichtigt zu arbeiten. (Referenzen sind immer noch Speicheradressen, stellen jedoch konzeptionell ein Gültigkeitsversprechen dar.)
std::unique_ptr
für einenstd::vector<std::unique_ptr>
Streit loswerden ?