Ich bin neu in der Verschiebung der Semantik in C ++ 11 und weiß nicht genau, wie ich mit unique_ptr
Parametern in Konstruktoren oder Funktionen umgehen soll . Betrachten Sie diese Klasse, die sich selbst referenziert:
#include <memory>
class Base
{
public:
typedef unique_ptr<Base> UPtr;
Base(){}
Base(Base::UPtr n):next(std::move(n)){}
virtual ~Base(){}
void setNext(Base::UPtr n)
{
next = std::move(n);
}
protected :
Base::UPtr next;
};
Soll ich so Funktionen schreiben, die unique_ptr
Argumente verwenden?
Und muss ich std::move
den aufrufenden Code verwenden?
Base::UPtr b1;
Base::UPtr b2(new Base());
b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead?
c++
arguments
c++11
unique-ptr
codablank1
quelle
quelle
Antworten:
Hier sind die möglichen Möglichkeiten, einen eindeutigen Zeiger als Argument zu verwenden, sowie die zugehörige Bedeutung.
(A) Nach Wert
Damit der Benutzer dies aufrufen kann, muss er einen der folgenden Schritte ausführen:
Wenn Sie einen eindeutigen Zeiger als Wert verwenden, übertragen Sie das Eigentum an dem Zeiger auf die betreffende Funktion / das betreffende Objekt / usw. Nach dem
newBase
BaunextBase
ist garantiert leer . Sie besitzen das Objekt nicht mehr und haben nicht einmal mehr einen Zeiger darauf. Es ist weg.Dies ist gewährleistet, da wir den Parameter als Wert nehmen. bewegt
std::move
eigentlich nichts; Es ist nur eine schicke Besetzung. Gibt a zurück , auf das ein r-Wert verweist . Das ist alles was es tut.std::move(nextBase)
Base&&
nextBase
Da
Base::Base(std::unique_ptr<Base> n)
C ++ sein Argument eher nach Wert als nach R-Wert-Referenz verwendet, erstellt es automatisch eine temporäre Datei für uns. Es entsteht einstd::unique_ptr<Base>
aus demBase&&
wir die Funktion über gegeben habenstd::move(nextBase)
. Es ist die Konstruktion dieses Temporärs, die den Wert tatsächlichnextBase
in das Funktionsargument verschiebtn
.(B) Durch nicht konstante l-Wert-Referenz
Dies muss für einen tatsächlichen l-Wert (eine benannte Variable) aufgerufen werden. Es kann nicht mit einem temporären wie diesem aufgerufen werden:
Die Bedeutung ist dieselbe wie die Bedeutung jeder anderen Verwendung von nicht konstanten Referenzen: Die Funktion kann das Eigentum an dem Zeiger beanspruchen oder nicht . Angesichts dieses Codes:
Es gibt keine Garantie,
nextBase
die leer ist. Es kann leer sein; es darf nicht. Es kommt wirklich darauf an, was man machenBase::Base(std::unique_ptr<Base> &n)
will. Aus diesem Grund ist nicht nur anhand der Funktionssignatur ersichtlich, was passieren wird. Sie müssen die Implementierung (oder die zugehörige Dokumentation) lesen.Aus diesem Grund würde ich dies nicht als Schnittstelle vorschlagen.
(C) Durch konstante l-Wert-Referenz
Ich zeige keine Implementierung, da Sie nicht von einem wechseln können
const&
. Wenn Sie a übergebenconst&
, sagen Sie, dass die FunktionBase
über den Zeiger auf das zugreifen kann , es aber nirgendwo speichern kann. Es kann kein Eigentum daran beanspruchen.Dies kann nützlich sein. Nicht unbedingt für Ihren speziellen Fall, aber es ist immer gut, jemandem einen Zeiger geben zu können und zu wissen, dass er nicht (ohne gegen die Regeln von C ++ zu verstoßen, wie kein Wegwerfen
const
) das Eigentum daran beanspruchen kann. Sie können es nicht speichern. Sie können es an andere weitergeben, aber diese anderen müssen sich an dieselben Regeln halten.(D) Durch r-Wert-Referenz
Dies ist mehr oder weniger identisch mit dem Fall "durch nicht konstante l-Wert-Referenz". Die Unterschiede sind zwei Dinge.
Sie können eine vorübergehende übergeben:
Sie müssen verwenden,
std::move
wenn Sie nicht temporäre Argumente übergeben.Letzteres ist wirklich das Problem. Wenn Sie diese Zeile sehen:
Sie haben eine vernünftige Erwartung, dass diese Zeile nach Abschluss
nextBase
leer sein sollte. Es hätte verschoben werden sollen. Immerhinstd::move
sitzt das da und sagt dir, dass Bewegung stattgefunden hat.Das Problem ist, dass es nicht hat. Es ist nicht garantiert , dass es verschoben wurde. Es wurde möglicherweise verschoben, aber Sie werden es nur anhand des Quellcodes erkennen. Sie können nicht nur an der Funktionssignatur erkennen.
Empfehlungen
unique_ptr
, nehmen Sie es nach Wert.unique_ptr
für die Dauer der Ausführung dieser Funktion verwenden möchten, nehmen Sie sie durchconst&
. Alternativ können Sie a&
oderconst&
an den tatsächlichen Typ übergeben, auf den verwiesen wird, anstatt a zu verwendenunique_ptr
.&&
. Ich rate jedoch dringend davon ab, wann immer dies möglich ist.So manipulieren Sie unique_ptr
Sie können a nicht kopieren
unique_ptr
. Sie können es nur bewegen. Der richtige Weg, dies zu tun, ist mit derstd::move
Standardbibliotheksfunktion.Wenn Sie einen
unique_ptr
By-Wert nehmen, können Sie sich frei davon bewegen. Aber Bewegung passiert eigentlich nicht wegenstd::move
. Nehmen Sie die folgende Aussage:Das sind wirklich zwei Aussagen:
(Hinweis: Der obige Code wird technisch nicht kompiliert, da nicht temporäre R-Wert-Referenzen keine R-Werte sind. Er dient nur zu Demozwecken.)
Dies
temporary
ist nur ein R-Wert-Verweis aufoldPtr
. Es ist im Konstruktor vonnewPtr
wo die Bewegung geschieht.unique_ptr
Der Bewegungskonstruktor (ein Konstruktor, der a&&
für sich nimmt) ist das, was die eigentliche Bewegung bewirkt.Wenn Sie einen haben
unique_ptr
Wert und Sie wollen es irgendwo zu speichern, Sie müssen verwendenstd::move
den Speicher zu tun.quelle
std::move
nicht den Rückgabewert. Denken Sie daran, dass benannte rWertreferenzen lWerte sind. ideone.com/VlEM3unique_ptr
; Möglicherweise benötigen einige andere Aufrufer dieselbe Funktionalität, halten jedochshared_ptr
stattdessen einen Aufruf.] (2) Aufruf durch lWertreferenz könnte nützlich sein, wenn die aufgerufene Funktion den Zeiger ändert , z. B. das Hinzufügen oder Entfernen von (im Besitz einer Liste befindlichen) Knoten zu einer verknüpften Liste.unique_ptr
Werte immer von rvalue-Referenzen übergibt (zum Beispiel bei der Umwandlung inshared_ptr
). Der Grund dafür könnte sein, dass es etwas effizienter ist (es wird nicht zu temporären Zeigern gewechselt), während es dem Aufrufer genau die gleichen Rechte einräumt (kann r-Werte oder eingewickelte l-Werte übergebenstd::move
, aber keine nackten l-Werte).Lassen Sie mich versuchen, die verschiedenen möglichen Modi für die Weitergabe von Zeigern an Objekte anzugeben, deren Speicher von einer Instanz der
std::unique_ptr
Klassenvorlage verwaltet wird .std::auto_ptr
Dies gilt auch für die ältere Klassenvorlage (die meines Erachtens alle Verwendungszwecke dieses eindeutigen Zeigers zulässt, für die jedoch zusätzlich modifizierbare l-Werte akzeptiert werden, wenn r-Werte erwartet werden, ohne dass sie aufgerufen werden müssenstd::move
) und in gewissem Umfang auch fürstd::shared_ptr
.Als konkretes Beispiel für die Diskussion werde ich den folgenden einfachen Listentyp betrachten
Instanzen einer solchen Liste (die Teile nicht mit anderen Instanzen teilen oder zirkulär sein dürfen) gehören vollständig demjenigen, der den Anfangszeiger hält
list
. Wenn der Clientcode weiß, dass die darin gespeicherte Liste niemals leer sein wird, kann er auch die erstenode
direkt anstelle von a speichernlist
. Esnode
muss kein Destruktor für definiert werden: Da die Destruktoren für seine Felder automatisch aufgerufen werden, wird die gesamte Liste vom Smart-Zeiger-Destruktor rekursiv gelöscht, sobald die Lebensdauer des anfänglichen Zeigers oder Knotens endet.Dieser rekursive Typ bietet die Gelegenheit, einige Fälle zu diskutieren, die im Fall eines intelligenten Zeigers auf einfache Daten weniger sichtbar sind. Auch die Funktionen selbst liefern gelegentlich (rekursiv) ein Beispiel für Client-Code. Das typedef für
list
ist natürlich voreingenommenunique_ptr
, aber die Definition könnte geändert werden, umauto_ptr
oder zu verwendenshared_ptr
stattdessen, ohne dass viel an dem unten Gesagten muss (insbesondere in Bezug auf Ausnahmesicherheit, ohne dass Destruktoren geschrieben werden müssen).Modi zum Weitergeben intelligenter Zeiger
Modus 0: Übergeben Sie einen Zeiger oder ein Referenzargument anstelle eines intelligenten Zeigers
Wenn sich Ihre Funktion nicht mit dem Besitz befasst, ist dies die bevorzugte Methode: Lassen Sie sie überhaupt keinen intelligenten Zeiger verwenden. In diesem Fall muss sich Ihre Funktion keine Sorgen machen, wem das Objekt gehört, auf das verwiesen wird, oder auf welche Weise das Eigentum verwaltet wird. Daher ist die Übergabe eines Rohzeigers sowohl absolut sicher als auch die flexibelste Form, da ein Client unabhängig vom Besitz immer kann Erstellen Sie einen Rohzeiger (entweder durch Aufrufen der
get
Methode oder über den Adressoperator)&
).Zum Beispiel sollte die Funktion zum Berechnen der Länge einer solchen Liste kein
list
Argument, sondern ein Rohzeiger sein:Ein Client, der eine Variable enthält,
list head
kann diese Funktion als aufrufenlength(head.get())
, während ein Client, der stattdessennode n
eine nicht leere Liste gespeichert hat, aufrufen kannlength(&n)
.Wenn garantiert ist, dass der Zeiger nicht null ist (was hier nicht der Fall ist, da Listen möglicherweise leer sind), kann es vorziehen, eine Referenz anstelle eines Zeigers zu übergeben. Dies kann ein Zeiger / Verweis auf Nicht-
const
Knoten sein, wenn die Funktion den Inhalt der Knoten aktualisieren muss, ohne einen von ihnen hinzuzufügen oder zu entfernen (letzterer würde den Besitz beinhalten).Ein interessanter Fall, der in die Kategorie Modus 0 fällt, ist das Erstellen einer (tiefen) Kopie der Liste. Während eine Funktion, die dies tut, natürlich das Eigentum an der von ihr erstellten Kopie übertragen muss, geht es ihr nicht um das Eigentum an der Liste, die sie kopiert. Es könnte also wie folgt definiert werden:
Dieser Code
copy
verdient eine genaue Betrachtung, sowohl für die Frage, warum er überhaupt kompiliert wird (das Ergebnis des rekursiven Aufrufs von in der Initialisiererliste bindet an das Referenzargument rvalue im Verschiebungskonstruktor vonunique_ptr<node>
, auch bekanntlist
als, wenn dasnext
Feld des initialisiert wird generiertnode
) und für die Frage, warum es ausnahmesicher ist (wenn während des rekursiven Zuordnungsprozesses der Speicher knapp wird und einige Aufrufe vonnew
Würfen ausgeführt werdenstd::bad_alloc
, wird zu diesem Zeitpunkt ein Zeiger auf die teilweise erstellte Liste anonym in einem temporären Typ gehaltenlist
erstellt für die Initialisiererliste, und ihr Destruktor bereinigt diese Teilliste). Übrigens sollte man der Versuchung widerstehen, (wie ich es ursprünglich tat) die zweitenullptr
durch zu ersetzenp
, von dem schließlich bekannt ist, dass es an diesem Punkt null ist: Man kann keinen intelligenten Zeiger von einem (rohen) Zeiger auf eine Konstante konstruieren , selbst wenn bekannt ist, dass er null ist.Modus 1: Übergeben Sie einen Smart Pointer als Wert
Eine Funktion, die einen Smart-Pointer-Wert als Argument verwendet, nimmt das Objekt in Besitz, auf das sofort verwiesen wird: Der Smart-Pointer, den der Aufrufer gehalten hat (ob in einer benannten Variablen oder einer anonymen temporären Datei), wird am Funktionseingang und beim Aufruf des Aufrufers in den Argumentwert kopiert Der Zeiger ist null geworden (im Falle einer temporären Kopie wurde die Kopie möglicherweise entfernt, aber in jedem Fall hat der Aufrufer den Zugriff auf das Objekt verloren, auf das verwiesen wird). Ich möchte diesen Modus- Anruf mit Bargeld anrufen : Der Anrufer zahlt im Voraus für den angerufenen Dienst und kann sich nach dem Anruf keine Illusionen über den Besitz machen. Um dies zu verdeutlichen, muss der Aufrufer nach den Sprachregeln das Argument einschließen
std::move
wenn der intelligente Zeiger in einer Variablen gehalten wird (technisch gesehen, wenn das Argument ein l-Wert ist); In diesem Fall (jedoch nicht für Modus 3 unten) führt diese Funktion das aus, was der Name andeutet, nämlich den Wert von der Variablen in eine temporäre zu verschieben, wobei die Variable null bleibt.Für Fälle , in denen die genannte Funktion nimmt bedingungslos Eigentum an (stibitzt) das spitze-zu - Objekt, wird dieser Modus verwendet , mit
std::unique_ptr
oderstd::auto_ptr
ist ein guter Weg , um einen Zeiger zusammen mit seinem Eigentum an vorbei, die das Risiko von Speicherlecks vermieden werden . Dennoch denke ich, dass es nur sehr wenige Situationen gibt, in denen der folgende Modus 3 nicht (nur geringfügig) dem Modus 1 vorzuziehen ist. Aus diesem Grund werde ich keine Anwendungsbeispiele für diesen Modus geben. (Siehe jedoch das folgendereversed
Beispiel für Modus 3, in dem angemerkt wird, dass Modus 1 mindestens genauso gut funktioniert.) Wenn die Funktion mehr Argumente als nur diesen Zeiger verwendet, kann es vorkommen, dass es zusätzlich einen technischen Grund gibt, den Modus zu vermeiden 1 (mitstd::unique_ptr
oderstd::auto_ptr
): da eine tatsächliche Verschiebungsoperation stattfindet, während eine Zeigervariable übergeben wirdp
Durch den Ausdruckstd::move(p)
kann nicht angenommen werden, dassp
er bei der Bewertung der anderen Argumente einen nützlichen Wert hat (die Reihenfolge der Bewertung ist nicht angegeben), was zu subtilen Fehlern führen kann. Im Gegensatz dazu stellt die Verwendung von Modus 3 sicher, dassp
vor dem Funktionsaufruf keine Verschiebung von erfolgt, sodass andere Argumente sicher auf einen Wert zugreifen könnenp
.Bei Verwendung mit
std::shared_ptr
ist dieser Modus insofern interessant, als der Aufrufer mit einer einzigen Funktionsdefinition auswählen kann, ob eine Freigabekopie des Zeigers für sich behalten werden soll, während eine neue Freigabekopie erstellt wird, die von der Funktion verwendet werden soll (dies geschieht, wenn ein l-Wert vorliegt Argument wird bereitgestellt, der beim Aufruf verwendete Kopierkonstruktor für gemeinsam genutzte Zeiger erhöht die Referenzanzahl) oder um der Funktion nur eine Kopie des Zeigers zu geben, ohne einen beizubehalten oder die Referenzanzahl zu berühren (dies geschieht möglicherweise, wenn ein rvalue-Argument angegeben wird ein Wert, der in einen Aufruf vonstd::move
) eingewickelt ist . Zum BeispielDasselbe könnte erreicht werden, indem
void f(const std::shared_ptr<X>& x)
(für den Fall lvalue) undvoid f(std::shared_ptr<X>&& x)
(für den Fall rvalue) getrennt definiert werden , wobei sich die Funktionskörper nur dadurch unterscheiden, dass die erste Version die Kopiersemantik aufruft (bei Verwendung der Kopierkonstruktion / -zuweisung),x
während die zweite Version die Bewegungssemantik verschiebt (Schreibenstd::move(x)
stattdessen wie im Beispielcode). Für gemeinsam genutzte Zeiger kann Modus 1 hilfreich sein, um eine gewisse Codeduplizierung zu vermeiden.Modus 2: Übergeben Sie einen Smart Pointer als (modifizierbare) Wertreferenz
Hier erfordert die Funktion lediglich einen veränderbaren Verweis auf den Smart Pointer, gibt jedoch keinen Hinweis darauf, was er damit tun wird. Ich möchte diese Methode Call by Card aufrufen : Der Anrufer stellt die Zahlung durch Angabe einer Kreditkartennummer sicher. Die Referenz kann verwendet werden, um das Eigentum an dem Objekt zu übernehmen, auf das verwiesen wird, muss es aber nicht. Dieser Modus erfordert die Bereitstellung eines modifizierbaren lvalue-Arguments, das der Tatsache entspricht, dass der gewünschte Effekt der Funktion das Belassen eines nützlichen Werts in der Argumentvariablen umfassen kann. Ein Aufrufer mit einem rvalue-Ausdruck, den er an eine solche Funktion übergeben möchte, müsste ihn in einer benannten Variablen speichern, um den Aufruf ausführen zu können, da die Sprache nur eine implizite Konvertierung in eine Konstante bietetlWertreferenz (bezogen auf eine temporäre) aus einem rWert. (Im Gegensatz zur umgekehrten Situation
std::move
ist eine Umwandlung vonY&&
bisY&
mitY
dem Smart-Pointer-Typ nicht möglich. Diese Konvertierung kann jedoch auf Wunsch durch eine einfache Vorlagenfunktion erzielt werden. Siehe https://stackoverflow.com/a/24868376 / 1436796 ). Für den Fall, dass die aufgerufene Funktion beabsichtigt, das Objekt bedingungslos zu übernehmen und das Argument zu stehlen, gibt die Verpflichtung zur Angabe eines lvalue-Arguments das falsche Signal: Die Variable hat nach dem Aufruf keinen nützlichen Wert. Daher sollte für eine solche Verwendung der Modus 3 bevorzugt werden, der innerhalb unserer Funktion identische Möglichkeiten bietet, die Anrufer jedoch auffordert, einen Wert anzugeben.Es gibt jedoch einen gültigen Anwendungsfall für Modus 2, nämlich Funktionen, die den Zeiger ändern können , oder das Objekt, auf das in einer Weise verwiesen wird, die Eigentum beinhaltet . Ein Beispiel
list
für eine solche Verwendung ist beispielsweise eine Funktion, die einem Knoten einen Präfix voranstellt :Es wäre hier natürlich unerwünscht, Anrufer zur Verwendung zu zwingen
std::move
, da ihr intelligenter Zeiger nach dem Anruf immer noch eine gut definierte und nicht leere Liste besitzt, wenn auch eine andere als zuvor.Auch hier ist es interessant zu beobachten, was passiert, wenn der
prepend
Anruf mangels freien Speichers fehlschlägt. Dann wird dernew
Anruf werfenstd::bad_alloc
; Da zu diesem Zeitpunkt keinenode
zugewiesen werden konnte, ist es sicher, dass die übergebene r-Wert-Referenz (Modus 3) vonstd::move(l)
noch nicht gestohlen werden kann, da dies getan würde, um dasnext
Feld dernode
nicht zugewiesenen zu erstellen. Der ursprüngliche intelligente Zeiger enthält alsol
immer noch die ursprüngliche Liste, wenn der Fehler ausgelöst wird. Diese Liste wird entweder vom Smart Pointer Destructor ordnungsgemäß zerstört oder enthält die ursprüngliche Liste,l
falls sie dank einer ausreichend frühencatch
Klausel überlebt .Das war ein konstruktives Beispiel; Mit einem Augenzwinkern auf diese Frage kann man auch das destruktivere Beispiel für das Entfernen des ersten Knotens mit einem bestimmten Wert geben, falls vorhanden:
Auch hier ist die Richtigkeit ziemlich subtil. Insbesondere wird in der letzten Anweisung der Zeiger
(*p)->next
, der in dem zu entfernenden Knoten enthalten ist, nicht verknüpft (vonrelease
, was den Zeiger zurückgibt, aber die ursprüngliche Null macht), bevorreset
(implizit) dieser Knoten zerstört wird (wenn er den alten Wert zerstört, der von gehalten wirdp
), wodurch sichergestellt wird, dass Zu diesem Zeitpunkt wird nur ein Knoten zerstört. (In der im Kommentar erwähnten alternativen Form würde dieser Zeitpunkt den Interna der Implementierung des Verschiebungszuweisungsoperators derstd::unique_ptr
Instanzlist
überlassen bleiben; der Standard besagt 20.7.1.2.3; 2, dass dieser Operator "so handeln soll, als ob von anrufenreset(u.release())
", woher sollte das Timing auch hier sicher sein.)Beachten Sie, dass
prepend
undremove_first
nicht von Clients aufgerufen werden können, die eine lokalenode
Variable für eine immer nicht leere Liste speichern , und das zu Recht, da die angegebenen Implementierungen in solchen Fällen nicht funktionieren könnten.Modus 3: Übergeben Sie einen Smart Pointer an eine (modifizierbare) Wertreferenz
Dies ist der bevorzugte Modus, wenn Sie einfach den Zeiger in Besitz nehmen. Ich möchte diese Methode call by check aufrufen : Der Anrufer muss die Unterzeichnung des Eigentums akzeptieren, als ob er Bargeld zur Verfügung stellen würde, indem er den Scheck unterschreibt. Die tatsächliche Auszahlung wird jedoch verschoben, bis die aufgerufene Funktion den Zeiger tatsächlich stiehlt (genau wie bei Verwendung von Modus 2) ). Das "Signieren des Schecks" bedeutet konkret, dass Anrufer ein Argument
std::move
einschließen müssen (wie in Modus 1), wenn es sich um einen Wert handelt (wenn es sich um einen Wert handelt, ist der Teil "Aufgeben des Eigentums" offensichtlich und erfordert keinen separaten Code).Beachten Sie, dass sich Modus 3 technisch genau wie Modus 2 verhält, sodass die aufgerufene Funktion nicht den Besitz übernehmen muss. Ich würde jedoch darauf bestehen , dass , wenn es eine Unsicherheit über die Eigentumsübertragung ist (bei normalem Gebrauch), Modus 2 sollte 3 bis Modus bevorzugt sein, dass so mit Modus 3 ist implizit ein Signal an Anrufer , dass sie sind Eigentum aufzugeben. Man könnte erwidern, dass nur das Übergeben von Argumenten im Modus 1 tatsächlich einen erzwungenen Verlust des Eigentums an Anrufer signalisiert. Wenn ein Client jedoch Zweifel an den Absichten der aufgerufenen Funktion hat, sollte er die Spezifikationen der aufgerufenen Funktion kennen, was jeden Zweifel beseitigen sollte.
Es ist überraschend schwierig, ein typisches Beispiel für unseren
list
Typ zu finden, der die Argumentübergabe im Modus 3 verwendet. Das Verschieben einer Listeb
an das Ende einer anderen Listea
ist ein typisches Beispiel. jedocha
(die überlebt und hält das Ergebnis der Operation) hindurchgeführt ist besser Modus 2:Ein reines Beispiel für die Übergabe von Argumenten im Modus 3 ist das folgende, das eine Liste (und deren Besitz) übernimmt und eine Liste mit den identischen Knoten in umgekehrter Reihenfolge zurückgibt.
Diese Funktion kann wie in aufgerufen werden
l = reversed(std::move(l));
, um die Liste in sich selbst umzukehren, aber die umgekehrte Liste kann auch anders verwendet werden.Hier wird das Argument aus Effizienzgründen sofort in eine lokale Variable verschoben (man hätte den Parameter
l
direkt anstelle von verwenden könnenp
, aber jeder Zugriff darauf würde jedes Mal eine zusätzliche Indirektionsebene erfordern); Daher ist der Unterschied zum Übergeben von Argumenten im Modus 1 minimal. In diesem Modus hätte das Argument tatsächlich direkt als lokale Variable dienen können, wodurch diese anfängliche Verschiebung vermieden wurde. Dies ist nur ein Beispiel für das allgemeine Prinzip, dass, wenn ein als Referenz übergebenes Argument nur dazu dient, eine lokale Variable zu initialisieren, man es genauso gut als Wert übergeben und den Parameter als lokale Variable verwenden kann.Die Verwendung von Modus 3 scheint vom Standard befürwortet zu werden, was durch die Tatsache belegt wird, dass alle bereitgestellten Bibliotheksfunktionen, die den Besitz von intelligenten Zeigern unter Verwendung von Modus 3 übertragen, ein besonders überzeugendes Beispiel dafür der Konstruktor sind
std::shared_ptr<T>(auto_ptr<T>&& p)
. Dieser Konstruktor verwendete (instd::tr1
), um eine modifizierbare lvalue- Referenz zu verwenden (genau wie derauto_ptr<T>&
Kopierkonstruktor), und konnte daher mit einemauto_ptr<T>
lvaluep
wie in aufgerufen werdenstd::shared_ptr<T> q(p)
, wonachp
er auf null zurückgesetzt wurde. Aufgrund des Wechsels von Modus 2 zu 3 bei der Argumentübergabe muss dieser alte Code jetzt neu geschrieben werdenstd::shared_ptr<T> q(std::move(p))
und funktioniert dann weiter. Ich verstehe, dass das Komitee den Modus 2 hier nicht mochte, aber sie hatten die Möglichkeit, durch Definition in Modus 1 zu wechselnstd::shared_ptr<T>(auto_ptr<T> p)
Stattdessen hätten sie sicherstellen können, dass alter Code ohne Änderung funktioniert, da (im Gegensatz zu eindeutigen Zeigern) Auto-Zeiger stillschweigend auf einen Wert dereferenziert werden können (das Zeigerobjekt selbst wird dabei auf Null zurückgesetzt). Anscheinend hat das Komitee es so sehr vorgezogen, Modus 3 gegenüber Modus 1 zu befürworten, dass es sich dafür entschieden hat, vorhandenen Code aktiv zu brechen, anstatt Modus 1 selbst für eine bereits veraltete Verwendung zu verwenden.Wann sollte Modus 3 gegenüber Modus 1 bevorzugt werden?
Modus 1 ist in vielen Fällen perfekt verwendbar und kann gegenüber Modus 3 in Fällen bevorzugt werden, in denen die Annahme des Eigentums ansonsten die Form des Verschiebens des intelligenten Zeigers auf eine lokale Variable wie im
reversed
obigen Beispiel annehmen würde . Ich kann jedoch zwei Gründe dafür sehen, Modus 3 im allgemeineren Fall zu bevorzugen:Es ist etwas effizienter, eine Referenz zu übergeben, als eine temporäre Referenz zu erstellen und den alten Zeiger nicht zu verwenden (der Umgang mit Bargeld ist etwas mühsam). In einigen Szenarien kann der Zeiger mehrmals unverändert an eine andere Funktion übergeben werden, bevor er tatsächlich gestohlen wird. Ein solches Übergeben erfordert im Allgemeinen das Schreiben
std::move
(es sei denn, Modus 2 wird verwendet). Beachten Sie jedoch, dass dies nur eine Besetzung ist, die eigentlich nichts tut (insbesondere keine Dereferenzierung), sodass keine Kosten anfallen.Sollte es denkbar sein, dass irgendetwas eine Ausnahme zwischen dem Start des Funktionsaufrufs und dem Punkt auslöst, an dem es (oder ein enthaltener Aufruf) das Objekt, auf das verwiesen wird, tatsächlich in eine andere Datenstruktur verschiebt (und diese Ausnahme ist nicht bereits in der Funktion selbst gefangen ), dann wird bei Verwendung von Modus 1 das Objekt, auf das der Smart Pointer verweist, zerstört, bevor eine
catch
Klausel die Ausnahme behandeln kann (da der Funktionsparameter beim Abwickeln des Stapels zerstört wurde), bei Verwendung von Modus 3 jedoch nicht. Letzteres gibt die Der Anrufer hat in solchen Fällen die Möglichkeit, die Daten des Objekts wiederherzustellen (indem er die Ausnahme abfängt). Beachten Sie, dass Modus 1 hier keinen Speicherverlust verursacht , aber zu einem nicht behebbaren Datenverlust für das Programm führen kann, was ebenfalls unerwünscht sein kann.Rückgabe eines intelligenten Zeigers: immer nach Wert
Um ein Wort über die Rückgabe eines intelligenten Zeigers zu schließen, der vermutlich auf ein Objekt zeigt, das für die Verwendung durch den Aufrufer erstellt wurde. Dies ist nicht wirklich ein Fall, der mit der Übergabe von Zeigern an Funktionen vergleichbar ist, aber der Vollständigkeit halber möchte ich darauf bestehen, dass in solchen Fällen immer nach Wert zurückgegeben wird (und nicht
std::move
in derreturn
Anweisung verwendet wird). Niemand möchte einen Verweis auf einen Zeiger erhalten, der wahrscheinlich gerade nicht gemixt wurde.quelle
unique_ptr
es verschoben wurde oder nicht, wird der Wert trotzdem gut gelöscht, wenn er bei jeder Zerstörung oder Wiederverwendung noch gespeichert wird.unique_ptr
einen Speicherverlust verhindert (und somit in gewissem Sinne seinen Vertrag erfüllt), aber hier (dh unter Verwendung von Modus 1) kann es (unter bestimmten Umständen) etwas verursachen, das als noch schädlicher angesehen werden kann , nämlich ein Datenverlust (Zerstörung des angegebenen Wertes), der mit Modus 3 hätte vermieden werden können.Ja, das müssen Sie, wenn Sie den
unique_ptr
by-Wert im Konstruktor übernehmen. Explizität ist eine schöne Sache. Daunique_ptr
es nicht kopierbar ist (Private Copy Ctor), sollte das, was Sie geschrieben haben, Ihnen einen Compilerfehler geben.quelle
Bearbeiten: Diese Antwort ist falsch, obwohl der Code genau genommen funktioniert. Ich lasse es nur hier, weil die Diskussion darunter zu nützlich ist. Diese andere Antwort ist die beste Antwort zum Zeitpunkt meiner letzten Bearbeitung: Wie übergebe ich ein unique_ptr-Argument an einen Konstruktor oder eine Funktion?
Die Grundidee von
::std::move
ist, dass Leute, die an Ihnen vorbeikommen, es verwendenunique_ptr
sollten, um das Wissen auszudrücken, dass sie wissen, dass sieunique_ptr
vorbeikommen, und das Eigentum verlieren.Dies bedeutet, dass Sie
unique_ptr
in Ihren Methoden einen r-Wert-Verweis auf a verwenden sollten, nicht auf aunique_ptr
selbst. Dies funktioniert sowieso nicht, da für die Übergabe einer einfachen altenunique_ptr
Version eine Kopie erstellt werden muss. Dies ist in der Benutzeroberfläche für ausdrücklich verbotenunique_ptr
. Interessanterweise verwandelt die Verwendung einer benannten rvalue-Referenz diese wieder in einen lvalue, sodass Sie sie auch::std::move
in Ihren Methoden verwenden müssen.Dies bedeutet, dass Ihre beiden Methoden folgendermaßen aussehen sollten:
Dann würden Leute, die die Methoden anwenden, dies tun:
Wie Sie sehen,
::std::move
drückt das aus, dass der Zeiger an dem Punkt den Besitz verlieren wird, an dem es am relevantesten und hilfreichsten ist, dies zu wissen. Wenn dies unsichtbar passiert, wäre es für Leute, die Ihre Klasse benutzen, sehr verwirrend,objptr
plötzlich ohne ersichtlichen Grund den Besitz zu verlieren.quelle
Base fred(::std::move(objptr));
und nichtBase::UPtr fred(::std::move(objptr));
?std::move
sowohl den Konstruktor als auch die Methode implementieren. Und selbst wenn Sie einen Wert übergeben, muss der Aufrufer immer nochstd::move
lvalues übergeben. Der Hauptunterschied besteht darin, dass diese Schnittstelle mit dem Pass-by-Value klar macht, dass das Eigentum verloren geht. Siehe Nicol Bolas Kommentar zu einer anderen Antwort.sollte viel besser sein als
und
sollte sein
mit dem gleichen Körper.
Und ... was ist
evt
inhandle()
??quelle
std::forward
hier hat keinen Vorteil : EsBase::UPtr&&
handelt sich immer um einen r-Wert-Referenztyp,std::move
der als r-Wert übergeben wird. Es ist bereits richtig weitergeleitet.unique_ptr
By-Wert annimmt, wird Ihnen garantiert, dass ein Verschiebungskonstruktor für den neuen Wert aufgerufen wurde (oder einfach, dass Sie einen temporären Wert erhalten haben). Dadurch wird sichergestellt, dass dieunique_ptr
Variable des Benutzers jetzt leer ist . Wenn Sie es&&
stattdessen nehmen, wird es nur geleert, wenn Ihr Code eine Verschiebungsoperation aufruft. Auf Ihre Weise ist es möglich, dass die Variable, von der der Benutzer nicht verschoben wurde. Das macht den Benutzerstd::move
verdächtig und verwirrend. Die Verwendungstd::move
sollte immer sicherstellen, dass etwas bewegt wurde .Nach oben gewählte Antwort. Ich ziehe es vor, an einer Wertreferenz vorbeizukommen.
Ich verstehe, was das Problem beim Übergeben einer R-Wert-Referenz verursachen kann. Aber lassen Sie uns dieses Problem auf zwei Seiten aufteilen:
Ich muss Code schreiben
Base newBase(std::move(<lvalue>))
oderBase newBase(<rvalue>)
.Der Bibliotheksautor sollte garantieren, dass der unique_ptr tatsächlich verschoben wird, um das Mitglied zu initialisieren, wenn er das Eigentum besitzen möchte.
Das ist alles.
Wenn Sie die rvalue-Referenz übergeben, wird nur eine "move" -Anweisung aufgerufen. Wenn Sie jedoch den Wert übergeben, sind es zwei.
Ja, wenn der Bibliotheksautor kein Experte in diesem Bereich ist, kann er unique_ptr möglicherweise nicht verschieben, um das Mitglied zu initialisieren, aber es ist das Problem des Autors, nicht Sie. Unabhängig davon, ob es sich um einen Wert oder eine Wertreferenz handelt, ist Ihr Code derselbe!
Wenn Sie eine Bibliothek schreiben, wissen Sie jetzt, dass Sie dies garantieren sollten. Tun Sie es einfach. Die Übergabe der rvalue-Referenz ist eine bessere Wahl als der Wert. Der Client, der Ihre Bibliothek verwendet, schreibt nur denselben Code.
Nun zu Ihrer Frage. Wie übergebe ich ein unique_ptr-Argument an einen Konstruktor oder eine Funktion?
Sie wissen, was die beste Wahl ist.
http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html
quelle