Ich habe meinen ersten Versuch, C ++ 11 zu verwenden unique_ptr
. Ich ersetze einen polymorphen Rohzeiger in einem meiner Projekte, das einer Klasse gehört, aber ziemlich häufig herumgereicht wird.
Früher hatte ich Funktionen wie:
bool func(BaseClass* ptr, int other_arg) {
bool val;
// plain ordinary function that does something...
return val;
}
Aber ich merkte schnell, dass ich nicht wechseln konnte zu:
bool func(std::unique_ptr<BaseClass> ptr, int other_arg);
Da der Aufrufer den Zeigerbesitz auf die Funktion übernehmen müsste, was ich nicht will. Was ist die beste Lösung für mein Problem?
Ich dachte daran, den Zeiger wie folgt zu übergeben:
bool func(const std::unique_ptr<BaseClass>& ptr, int other_arg);
Aber ich fühle mich sehr unwohl dabei, erstens, weil es nicht instinktiv erscheint, etwas zu übergeben, das bereits _ptr
als Referenz eingegeben wurde , was eine Referenz einer Referenz wäre. Zweitens, weil die Funktionssignatur noch größer wird. Drittens, weil im generierten Code zwei aufeinanderfolgende Zeiger-Indirektionen erforderlich wären, um meine Variable zu erreichen.
quelle
std::unique_ptr
für einenstd::vector<std::unique_ptr>
Streit loswerden ?Der Vorteil der Verwendung
std::unique_ptr<T>
(abgesehen davon, dass Sie nicht daran denken müssen, aufzurufendelete
oderdelete[]
explizit) ist, dass sie garantiert, dass ein Zeiger entwedernullptr
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):Dann wird der Anrufer, der ein hat
std::shared_ptr<BaseClass> ptr
, entweder dennullptr
Fall behandeln oder ihn bittenbool func(...)
, das Ergebnis zu berechnen: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 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öhtshared_ptr
und nicht möglich istunique_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.)
quelle
Ich stimme Martinho zu, aber ich denke, es ist wichtig, auf die Besitzersemantik eines Pass-by-Reference hinzuweisen. Ich denke, die richtige Lösung besteht darin, hier eine einfache Referenz zu verwenden:
Die allgemein akzeptierte Bedeutung einer Referenzübergabe in C ++ ist so, als ob der Aufrufer der Funktion der Funktion mitteilt: "Hier können Sie dieses Objekt ausleihen, verwenden und ändern (wenn nicht const), aber nur für die Dauer des Funktionskörpers. " Dies steht in keiner Weise im Widerspruch zu den Eigentumsregeln von,
unique_ptr
da das Objekt lediglich für einen kurzen Zeitraum ausgeliehen wird und keine tatsächliche Eigentumsübertragung stattfindet (wenn Sie Ihr Auto an jemanden verleihen, unterschreiben Sie den Titel zu ihm rüber?).Auch wenn es schlecht erscheint (in Bezug auf Design, Codierungspraktiken usw.), die Referenz (oder sogar den Rohzeiger) aus dem zu ziehen
unique_ptr
, liegt dies nicht daran, dass sie perfekt mit den von festgelegten Regeln für den Besitz übereinstimmt dieunique_ptr
. Und dann gibt es natürlich noch andere nette Vorteile, wie eine saubere Syntax, keine Beschränkung auf nur Objekte, die a gehörenunique_ptr
, und so weiter.quelle
Persönlich vermeide ich es, eine Referenz von einem Zeiger / Smart Pointer zu ziehen. Denn was passiert, wenn der Zeiger ist
nullptr
? Wenn Sie die Signatur in Folgendes ändern:Möglicherweise müssen Sie Ihren Code vor Nullzeiger-Dereferenzen schützen:
Wenn die Klasse der alleinige Eigentümer des Zeigers ist, erscheint die zweite Alternative von Martinho vernünftiger:
Alternativ können Sie verwenden
std::shared_ptr
. Wenn jedoch nur eine einzige Stelle verantwortlich istdelete
,std::shared_ptr
zahlt sich der Overhead nicht aus.quelle
std::unique_ptr
kein Overhead anfällt ?std::shared_ptr
Overhead erzählt .