Wie übergebe ich ein unique_ptr Argument an einen Konstruktor oder eine function?

Ich bin neu, um Semantik in C ++ 11 zu bewegen, und ich weiß nicht sehr gut, wie man unique_ptr Parameter in Konstruktoren oder functionen behandelt. Stellen Sie sich diese class selbst vor:

 #include  class Base { public: typedef unique_ptr 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; }; 

Ist das wie ich functionen schreiben sollte, die unique_ptr Argumente nehmen?

Und muss ich std::move im aufrufenden Code verwenden?

 Base::UPtr b1; Base::UPtr b2(new Base()); b1->setNext(b2); //should I write b1->setNext(std::move(b2)); instead? 

   

    Hier sind die möglichen Möglichkeiten, einen eindeutigen pointers als Argument und die damit verbundene Bedeutung zu verwenden.

    (A) Nach Wert

     Base(std::unique_ptr n) : next(std::move(n)) {} 

    Damit der Benutzer dies aufrufen kann, müssen sie einen der folgenden Schritte ausführen:

     Base newBase(std::move(nextBase)); Base fromTemp(std::unique_ptr(new Base(...)); 

    Wenn Sie einen eindeutigen pointers als Wert verwenden, bedeutet dies, dass Sie den Besitzer des pointerss auf die fragliche function / das betreffende Objekt / etc übertragen. Nachdem newBase ist nextBase garantiert leer . Sie besitzen das Objekt nicht, und Sie haben nicht einmal einen pointers darauf. Es ist weg.

    Dies ist gewährleistet, da wir den Parameter nach Wert nehmen. std::move eigentlich nichts; es ist nur eine schicke Besetzung. std::move(nextBase) gibt eine Base&& , die ein r-Wert-Verweis auf nextBase . Mehr ist es nicht.

    Da Base::Base(std::unique_ptr n) sein Argument nach Wert und nicht nach r-Wert-Referenz nimmt, erstellt C ++ automatisch ein temporäres für uns. Es erzeugt eine std::unique_ptr von der Base&& , die wir der function über std::move(nextBase) . Es ist die Konstruktion dieses Temporären, die den Wert von nextBase in das functionsargument n .

    (B) Durch nicht-konstante l-Wert-Referenz

     Base(std::unique_ptr &n) : next(std::move(n)) {} 

    Dies muss auf einem tatsächlichen l-Wert (eine benannte Variable) aufgerufen werden. Es kann nicht mit einem temporären wie folgt aufgerufen werden:

     Base newBase(std::unique_ptr(new Base)); //Illegal in this case. 

    Die Bedeutung davon ist die gleiche wie die Bedeutung jeder anderen Verwendung von nicht-konformen Referenzen: Die function kann das Eigentum an dem pointers beanspruchen oder nicht . Mit diesem Code:

     Base newBase(nextBase); 

    Es gibt keine Garantie dafür, dass nextBase leer ist. Es kann leer sein; es darf nicht. Es hängt wirklich davon ab, was Base::Base(std::unique_ptr &n) möchte. Aus diesem Grund ist es nicht sehr offensichtlich, nur von der function Signatur, 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

     Base(std::unique_ptr const &n); 

    Ich zeige keine Implementierung, weil Sie nicht von einer const& . Indem Sie const& , sagen Sie, dass die function über den pointers auf die Base zugreifen kann, aber nicht überall gespeichert werden kann. Es kann nicht das Eigentumsrecht dafür beanspruchen.

    Dies kann nützlich sein. Nicht unbedingt für Ihren speziellen Fall, aber es ist immer gut, jemandem einen pointers geben zu können und zu wissen, dass er nicht (ohne Regeln von C ++ zu brechen, wie kein const ) die Eigentumsrechte dafür beanspruchen kann. Sie können es nicht speichern. Sie können es an andere weitergeben, aber diese anderen müssen sich an die gleichen Regeln halten.

    (D) Nach r-Wert-Referenz

     Base(std::unique_ptr &&n) : next(std::move(n)) {} 

    Dies ist mehr oder weniger identisch mit dem Fall “by non-const l-value reference”. Die Unterschiede sind zwei Dinge.

    1. Sie können eine temporäre übergeben:

       Base newBase(std::unique_ptr(new Base)); //legal now.. 
    2. Sie müssen std::move wenn Sie nicht temporäre Argumente übergeben.

    Letzteres ist wirklich das Problem. Wenn Sie diese Zeile sehen:

     Base newBase(std::move(nextBase)); 

    Sie haben eine vernünftige Erwartung, dass nach Abschluss dieser Zeile nextBase leer sein sollte. Es hätte verschoben werden sollen. Immerhin hast du das std::move sitzen und dir sagen, dass Bewegung stattgefunden hat.

    Das Problem ist, dass es nicht so ist. Es ist nicht garantiert , dass man von ihnen weggezogen wurde. Möglicherweise wurde es verschoben, aber Sie wissen es nur, wenn Sie sich den Quellcode ansehen. Sie können nicht nur von der functionssignatur unterscheiden.

    Empfehlungen

    • (A) Nach Wert: Wenn Sie meinen, dass eine function den Besitz eines unique_ptr beansprucht, nehmen Sie sie nach Wert.
    • (C) Durch const l-Wert Referenz: Wenn Sie für eine function bedeuten, die unique_ptr einfach für die Dauer der Ausführung dieser function zu verwenden, nehmen Sie sie mit const& . Alternativ übergeben Sie ein & oder const& an den tatsächlichen Typ, auf den gezeigt wird, anstatt einen unique_ptr .
    • (D) Nach r-Wert-Referenz: Wenn eine function Besitzansprüche geltend machen kann oder nicht (abhängig von internen Code-Pfaden), dann nehmen Sie sie mit && . Ich rate jedoch dringend davon ab, dies so oft wie möglich zu tun.

    Wie man unique_ptr manipuliert

    Sie können kein unique_ptr kopieren. Sie können es nur bewegen. Der richtige Weg, dies zu tun, ist mit der Standard-Bibliothek-function std::move .

    Wenn Sie ein unique_ptr nach Wert nehmen, können Sie sich frei davon bewegen. Aber Bewegung passiert nicht wirklich wegen std::move . Nehmen Sie die folgende Aussage vor:

     std::unique_ptr newPtr(std::move(oldPtr)); 

    Das sind wirklich zwei Aussagen:

     std::unique_ptr &&temporary = std::move(oldPtr); std::unique_ptr newPtr(temporary); 

    (Hinweis: Der obige Code wird technisch nicht kompiliert, da nicht-temporäre r-Wert-Referenzen nicht wirklich R-Werte sind. Er dient nur Demo-Zwecken).

    Das temporary ist nur ein r-Wert-Verweis auf oldPtr . Es ist im Konstruktor von newPtr wo die Bewegung passiert. Der move unique_ptr (ein Konstruktor, der ein && zu sich selbst nimmt) ist das, was die tatsächliche Bewegung tut.

    Wenn Sie einen unique_ptr Wert haben und ihn irgendwo speichern möchten, müssen Sie std::move , um den Speicher zu erstellen.

    Lassen Sie mich versuchen, die verschiedenen realisierbaren Arten der Übergabe von pointersn an Objekte anzugeben, deren Speicher von einer Instanz der classnvorlage std::unique_ptr verwaltet wird; es gilt auch für die ältere std::auto_ptr classnvorlage (von der ich glaube, dass sie alle Verwendungen dieses eindeutigen pointerss zulässt, für die aber auch änderbare lvalues ​​akzeptiert werden, wenn rvalues ​​erwartet werden, ohne dass std::move aufgerufen werden muss). und teilweise auch zu std::shared_ptr .

    Als konkretes Beispiel für die Diskussion werde ich den folgenden einfachen Listentyp betrachten

     struct node; typedef std::unique_ptr list; struct node { int entry; list next; } 

    Instanzen einer solchen Liste (die nicht Teile mit anderen Instanzen teilen dürfen oder zirkulär sein dürfen) gehören vollständig demjenigen, der den ursprünglichen Listenzeiger hält. Wenn der Client-Code weiß, dass die Liste, die er speichert, niemals leer ist, kann er auch auswählen, den ersten node direkt anstatt einer list zu speichern. Es muss kein Destruktor für den node definiert werden: Da die Destruktoren für seine Felder automatisch aufgerufen werden, wird die gesamte Liste vom Smartpointer-Destruktor rekursiv gelöscht, sobald die Lebensdauer des ursprünglichen pointerss oder Knotens endet.

    Dieser rekursive Typ bietet die Gelegenheit, einige Fälle zu diskutieren, die im Fall eines intelligenten pointerss auf einfache Daten weniger sichtbar sind. Auch die functionen selbst liefern gelegentlich (rekursiv) ein Beispiel für einen Client-Code. Die typedef- list ist natürlich auf unique_ptr , aber die Definition könnte geändert werden, um stattdessen auto_ptr oder shared_ptr verwenden, ohne viel zu ändern, was unten gesagt wird (insbesondere in Bezug auf die Ausnahmesicherheit ohne die Notwendigkeit, Destruktoren zu schreiben).

    Modi der Weitergabe von Smart-pointersn herum

    Modus 0: Übergeben Sie einen pointers oder ein Referenzargument anstelle eines intelligenten pointerss

    Wenn es Ihre function nicht um Eigentumsrechte geht, ist dies die bevorzugte Methode: Machen Sie es nicht mit einem intelligenten pointers. In diesem Fall braucht sich Ihre function keine Gedanken darüber zu machen, wem das Objekt gehört oder auf welche Weise die Eigentümerschaft verwaltet wird. Die Übergabe eines rohen Mauszeigers ist also absolut sicher und die flexibelste Form, da unabhängig vom Besitz ein Client immer kann Erzeugen Sie einen rohen pointers (entweder durch Aufruf der Methode get oder durch den Operator address-of).

    Zum Beispiel sollte die function zum Berechnen der Länge einer solchen Liste nicht ein Listenargument, sondern ein roher pointers sein:

     size_t length(const node* p) { size_t l=0; for ( ; p!=nullptr; p=p->next.get()) ++l; return l; } 

    Ein Client, der einen Variablenlistenkopf besitzt, kann diese function als length(head.get()) , während ein Client, der statt dessen einen node n für eine nicht leere Liste ausgewählt hat, die length(&n) aufrufen kann.

    Wenn garantiert ist, dass der pointers nicht null ist (was hier nicht der Fall ist, da Listen leer sein können), würde man vielleicht lieber einen Verweis als einen pointers übergeben. Es könnte ein pointers / Verweis auf nicht- const wenn die function den Inhalt des Knotens aktualisieren muss, ohne einen von ihnen hinzuzufügen oder zu entfernen (letzteres würde den Besitz betreffen).

    Ein interessanter Fall, der in die Kategorie des Modus 0 fällt, ist das Erstellen einer (tiefen) Kopie der Liste; während eine function, die dies tut, natürlich das Eigentum an der Kopie übertragen muss, die sie erstellt, ist es nicht mit dem Besitz der Liste, die sie kopiert, beschäftigt. So könnte es wie folgt definiert werden:

     list copy(const node* p) { return list( p==nullptr ? nullptr : new node{p->entry,copy(p->next.get())} ); } 

    Dieser Code verdient einen genauen Blick, sowohl für die Frage, warum er überhaupt kompiliert wird (das Ergebnis des rekursiven Aufrufs zum copy in der Initialisiererliste bindet an das rvalue-Referenzargument im Verschiebungskonstruktor von unique_ptr , alias list , wenn das next Feld des generierten node initialisiert wird, und für die Frage, warum es std::bad_alloc ist (wenn während des rekursiven Zuteilungsprozesses der Speicher ausgeht und ein Aufruf von new throws std::bad_alloc , dann ein pointers zu der teilweise konstruierten Liste wird anonym in einer temporären list die für die Initialisiererliste erstellt wurde, und ihr Destruktor wird diese unvollständige Liste bereinigen). Übrigens sollte man der Versuchung widerstehen, das zweite nullptr durch p zu ersetzen, was schließlich an sich bekanntlich null ist: man kann keinen intelligenten pointers von einem (rohen) pointers auf konstant , gerade nullptr wenn bekannt ist, dass es null ist.

    Modus 1: Übergeben Sie einen intelligenten pointers nach Wert

    Eine function, die einen intelligenten pointerswert als Argument annimmt, übernimmt das Objekt, auf das sofort hingewiesen wird: Der intelligente pointers, den der Aufrufer enthielt (in einer benannten Variablen oder einem anonymen temporären), wird in den Argumentwert beim functionsaufruf und beim Aufrufer kopiert Der pointers ist null geworden (im Falle eines temporären Exemplars könnte die Kopie entfernt worden sein, aber in jedem Fall hat der Aufrufer den Zugriff auf das auf das Objekt verwiesene Objekt verloren). Ich möchte diesen Modus- Anruf mit Bargeld anrufen : Anrufer zahlt für den angerufenen Dienst vorne und kann sich nach dem Anruf keine Illusionen über den Besitz machen. Um dies zu verdeutlichen, müssen die Sprachregeln das Argument in std::move umbrechen std::move wenn der Smart Pointer in einer Variablen enthalten ist (technisch gesehen, wenn das Argument ein Lvalue ist); In diesem Fall (aber nicht für Modus 3 unten) tut diese function, was ihr Name andeutet, nämlich den Wert von der Variablen in ein temporäres zu verschieben, wobei die Variable null bleibt.

    Für Fälle, in denen die aufgerufene function bedingungslos das Objekt übernimmt, ist dieser Modus, der mit std::unique_ptr oder std::auto_ptr wird, eine gute Möglichkeit, einen pointers zusammen mit seinem Besitz zu übergeben, wodurch jedes Risiko vermieden wird Speicherlecks. Nichtsdestotrotz denke ich, dass es nur sehr wenige Situationen gibt, in denen der Modus 3 unten (gegenüber dem Modus 1) nicht vorzuziehen ist. Aus diesem Grund werde ich keine Anwendungsbeispiele für diesen Modus bereitstellen. (Siehe jedoch das reversed Beispiel von Modus 3 unten, wo angemerkt wird, dass Modus 1 mindestens genauso gut wäre.) Wenn die function mehr Argumente als nur diesen pointers benötigt, kann es vorkommen, dass zusätzlich ein technischer Grund zu vermeiden ist Modus 1 (mit std::unique_ptr oder std::auto_ptr ): Da eine tatsächliche Verschiebungsoperation stattfindet, während eine pointersvariable p mit dem Ausdruck std::move(p) , kann nicht angenommen werden, dass p einen nützlichen Wert while enthält Bewerten der anderen Argumente (die Reihenfolge der Bewertung ist nicht spezifiziert), was zu subtilen Fehlern führen könnte; Im Gegensatz dazu stellt die Verwendung von Modus 3 sicher, dass keine Bewegung von p vor dem functionsaufruf stattfindet, so dass andere Argumente sicher auf einen Wert über p zugreifen können.

    Bei der Verwendung mit std::shared_ptr ist dieser Modus std::shared_ptr interessant, als der Aufrufer mit einer einzigen functionsdefinition wählen kann, ob er eine Kopie des Verweises für sich selbst behalten und dabei eine neue Freigabekopie erstellen soll, die von der function verwendet werden soll Wenn ein Lvalue-Argument angegeben wird, erhöht der Kopierkonstruktor für gemeinsam genutzte pointers beim Aufruf die Referenzzahl oder gibt der function nur eine Kopie des pointerss, ohne einen zu behalten oder die Referenzzahl zu berühren (dies geschieht bei einem Rvalue-Argument) wird bereitgestellt, möglicherweise ein lvalue, der in einen Aufruf von std::move eingepackt ist. Zum Beispiel

     void f(std::shared_ptr x) // call by shared cash { container.insert(std::move(x)); } // store shared pointer in container void client() { std::shared_ptr p = std::make_shared(args); f(p); // lvalue argument; store pointer in container but keep a copy f(std::make_shared(args)); // prvalue argument; fresh pointer is just stored away f(std::move(p)); // xvalue argument; p is transferred to container and left null } 

    Dasselbe könnte erreicht werden, indem man void f(const std::shared_ptr& x) (für den Fall lvalue) und void f(std::shared_ptr&& x) (für den Fall rvalue) mit definiert functionskörper, die sich nur darin unterscheiden, dass die erste Version eine Kopie-Semantik (unter Verwendung von Kopieaufbau / -zuweisung bei Verwendung von x ) aufruft, die zweite Version jedoch eine Semantik (statt wie im Beispielcode std::move(x) ). Für gemeinsam genutzte pointers kann Modus 1 nützlich sein, um eine gewisse Codeverdopplung zu vermeiden.

    Modus 2: Übergeben Sie einen Smart-pointers durch (modifizierbare) L-Wert-Referenz

    Hier benötigt die function lediglich einen veränderbaren Verweis auf den Smart Pointer, gibt aber keinen Hinweis darauf, was damit zu tun ist. Ich möchte diese Methode Call by Card nennen : Der Anrufer sichert die Zahlung durch Angabe einer Kreditkartennummer. Die Referenz kann verwendet werden, um das angegebene Objekt zu übernehmen, muss es aber nicht. Dieser Modus erfordert ein modifizierbares lvalue-Argument, das der Tatsache entspricht, dass der gewünschte Effekt der function darin bestehen kann, einen nützlichen Wert in der Argumentvariablen zu belassen. Ein Aufrufer mit einem rvalue-Ausdruck, den er an eine solche function ü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 lvalue-Referenz bereitstellt (bezogen auf eine temporäre Variable) ) von einem rvalue. (Im Gegensatz zu der umgekehrten Situation, die von std::move gehandhabt wird, ist eine Umwandlung von Y&& nach Y& mit Y der intelligente pointerstyp nicht möglich; nichtsdestoweniger könnte diese Konvertierung durch eine einfache Template-function erreicht werden, siehe https: // stackoverflow.com/a/24868376/1436796 ). Für den Fall, dass die aufgerufene function das Eigentum an dem Objekt bedingungslos übernehmen will, stiehlt das Argument, dass die Verpflichtung, ein Lvalue-Argument bereitzustellen, das falsche Signal gibt: Die Variable wird nach dem Aufruf keinen nützlichen Wert haben. Daher sollte Modus 3, der innerhalb unserer function identische Möglichkeiten bietet, aber den Anrufer auffordert, einen R-Wert bereitzustellen, für eine solche Verwendung bevorzugt sein.

    Es gibt jedoch einen gültigen Anwendungsfall für Modus 2, nämlich functionen, die den pointers modifizieren können, oder das Objekt, auf das in einer Weise gezeigt wird, die den Besitz betrifft . Zum Beispiel bietet eine function, die einen Knoten einer list voranstellt, ein Beispiel für eine solche Verwendung:

     void prepend (int x, list& l) { l = list( new node{ x, std::move(l)} ); } 

    Natürlich wäre es hier unerwünscht, Anrufer zu zwingen, std::move , da ihr intelligenter pointers nach dem Anruf immer noch eine gut definierte und nicht leere Liste besitzt, obwohl eine andere als zuvor.

    Wieder ist es interessant zu beobachten, was passiert, wenn der prepend Aufruf fehlschlägt, weil kein freier Speicher prepend . Dann wird der new Anruf std::bad_alloc casting; Da zu diesem Zeitpunkt kein node zugewiesen werden konnte, ist es sicher, dass die übergebene R-Wert-Referenz (Modus 3) von std::move(l) noch nicht gestohlen worden sein kann, wie dies zum Aufbau des next Feldes von der node , der nicht zugeordnet werden konnte Daher enthält der ursprüngliche intelligente pointers l immer noch die ursprüngliche Liste, wenn der Fehler ausgetriggers wird. Diese Liste wird entweder durch den intelligenten pointersdestruktor ordnungsgemäß zerstört, oder falls ich dank einer ausreichend frühen catch überleben sollte, wird sie immer noch die ursprüngliche Liste enthalten.

    Das war ein konstruktives Beispiel; Mit einem Augenzwinkern zu dieser Frage kann man auch das destruktivere Beispiel geben, um den ersten Knoten zu entfernen, der einen gegebenen Wert enthält, falls vorhanden:

     void remove_first(int x, list& l) { list* p = &l; while ((*p).get()!=nullptr and (*p)->entry!=x) p = &(*p)->next; if ((*p).get()!=nullptr) (*p).reset((*p)->next.release()); // or equivalent: *p = std::move((*p)->next); } 

    Auch hier ist die Korrektheit ziemlich subtil. Bemerkenswerterweise wird in der letzten statement der pointers (*p)->next innerhalb des zu entfernenden Knotens nicht verknüpft (durch release , der den pointers zurückgibt, sondern das Original null macht), bevor (implizit) dieser Knoten gelöscht wird (wenn er zerstört wird) der alte Wert von p ), der sicherstellt, dass zu diesem Zeitpunkt ein einziger Knoten zerstört wird. (In der im Kommentar erwähnten alternativen Form würde dieses Timing den Interna der Implementierung des move-assignment-Operators der Instanzliste std::unique_ptr überlassen; der Standard sagt 20.7.1.2.3; 2, dass dieser Operator dies tun sollte reset(u.release()) “als ob durch einen reset(u.release()) “, woher auch hier das Timing sicher sein sollte.)

    Beachten Sie, dass prepend und remove_first nicht von Clients aufgerufen werden können, die eine lokale node für eine immer nicht leere Liste speichern, und zwar zu Recht, da die angegebenen Implementierungen in solchen Fällen nicht funktionieren konnten.

    Modus 3: Übergeben Sie einen Smart-pointers durch (änderbare) R-Wert-Referenz

    Dies ist der bevorzugte Modus, den Sie verwenden, wenn Sie den pointers einfach übernehmen. Ich möchte diesen Methodenaufruf per Scheck aufrufen : Der Aufrufer muss akzeptieren, dass er das Eigentumsrecht aufgibt, als ob er Bargeld bereitstellt, indem er den Scheck unterschreibt, aber die tatsächliche Auszahlung wird verschoben, bis die aufgerufene function tatsächlich den pointers entwendet (genau wie bei Modus 2) ). Das “Signieren des Checks” bedeutet konkret, dass Anrufer ein Argument in std::move (wie in Modus 1) umbrechen müssen, wenn es sich um einen Lvalue handelt (wenn es ein rvalue ist, ist der Teil “Eigentümer aufgeben” offensichtlich und erfordert nein separater Code).

    Beachten Sie, dass sich der Modus 3 technisch genau wie der Modus 2 verhält, so dass die aufgerufene function nicht das Eigentum übernehmen muss; Ich würde jedoch darauf bestehen, dass bei einer Unsicherheit über die Eigentumsübertragung (bei normaler Nutzung) Modus 2 dem Modus 3 vorgezogen werden sollte, so dass die Verwendung von Modus 3 implizit ein Signal für Anrufer ist, dass sie den Besitz aufgeben. Man könnte erwidern, dass nur das Überschreiten von Mode 1-Argument einen erzwungenen Verlust des Eigentums an Anrufer signalisiert. Aber wenn ein Kunde Zweifel an den Absichten der aufgerufenen function hat, sollte er die Spezifikationen der aufgerufenen function kennen, was jeden Zweifel beseitigen sollte.

    Es ist überraschend schwierig, ein typisches Beispiel für unseren Listentyp zu finden, der Modus-3-Argumente verwendet. Verschieben einer Liste b an das Ende einer anderen Liste a ist ein typisches Beispiel; aber a (das überlebt und das Ergebnis der Operation hält) wird besser mit Modus 2 übergeben:

     void append (list& a, list&& b) { list* p=&a; while ((*p).get()!=nullptr) // find end of list a p=&(*p)->next; *p = std::move(b); // attach b; the variable b relinquishes ownership here } 

    Ein reines Beispiel für die Übergabe von Modus 3-Argumenten ist die folgende, die eine Liste (und deren Besitz) übernimmt und eine Liste zurückgibt, die die identischen Knoten in umgekehrter Reihenfolge enthält.

     list reversed (list&& l) noexcept // pilfering reversal of list { list p(l.release()); // move list into temporary for traversal list result(nullptr); while (p.get()!=nullptr) { // permute: result --> p->next --> p --> (cycle to result) result.swap(p->next); result.swap(p); } return result; } 

    Diese function könnte wie l = reversed(std::move(l)); aufgerufen werden: l = reversed(std::move(l)); um die Liste in sich umzukehren, aber die umgekehrte Liste kann auch anders verwendet werden.

    Hier wird das Argument sofort zur Effizienz in eine lokale Variable verschoben (man hätte den Parameter l direkt an der Stelle von p , aber dann würde jeder Zugriff auf eine zusätzliche Indirektionsebene führen); Daher ist der Unterschied bei der Übergabe von Modus 1 minimal. Bei Verwendung dieses Modus könnte das Argument direkt als lokale Variable gedient haben, wodurch diese anfängliche Bewegung vermieden wird; Dies ist nur eine Instanz des allgemeinen Prinzips, dass, wenn ein Argument, das durch Referenz übergeben wird, nur dazu dient, eine lokale Variable zu initialisieren, man sie genauso gut als Wert übergeben und den Parameter als lokale Variable verwenden könnte.

    Die Verwendung von Modus 3 scheint von dem Standard befürwortet zu werden, wie die Tatsache zeigt, dass alle bereitgestellten Bibliotheksfunktionen den Besitz von intelligenten pointersn mit Modus 3 übertragen. Ein besonderer überzeugender Fall ist der Konstruktor std::shared_ptr(auto_ptr&& p) . Dieser Konstruktor verwendet (in std::tr1 ) eine modifizierbare lvalue- Referenz (genau wie der auto_ptr& copy-Konstruktor) und könnte daher mit einem auto_ptr lvalue p wie in std::shared_ptr q(p) aufgerufen werden std::shared_ptr q(p) , nach dem p auf Null zurückgesetzt wurde. Wegen des Wechsels von Modus 2 zu 3 bei der Übergabe von Argumenten muss dieser alte Code nun in std::shared_ptr q(std::move(p)) und wird dann weiter arbeiten. Ich verstehe, dass das Komitee den Modus 2 hier nicht mochte, aber sie hatten die Möglichkeit, in Modus 1 zu wechseln, indem sie stattdessen std::shared_ptr(auto_ptr p) , sie hätten sicherstellen können, dass alter Code funktioniert ohne Änderung, da (im Gegensatz zu eindeutigen pointersn) Autozeiger automatisch auf einen Wert dereferenziert werden können (das pointersobjekt selbst wird dabei auf Null zurückgesetzt). Anscheinend hat das Komitee es so vorgezogen, Modus 3 gegenüber Modus 1 zu befürworten, dass sie sich dafür entschieden, bestehenden Code aktiv zu brechen, anstatt Modus 1 selbst für eine bereits veraltete Nutzung zu verwenden.

    Wann bevorzugen wir Modus 3 gegenüber Modus 1?

    Modus 1 ist in vielen Fällen perfekt verwendbar und könnte gegenüber Modus 3 in Fällen bevorzugt sein, in denen die Besitzübernahme ansonsten die Form annehmen würde, den intelligenten pointers auf eine lokale Variable zu verschieben, wie in dem reversed Beispiel oben. Ich kann jedoch zwei Gründe sehen, Modus 3 im allgemeineren Fall vorzuziehen:

    • Es ist etwas effizienter, eine Referenz zu übergeben, als ein temporäres und nix den alten pointers (Umgang mit Bargeld ist etwas mühsam) zu erstellen; In einigen Szenarien kann der pointers mehrere Male unverändert an eine andere function übergeben werden, bevor er tatsächlich gestohlen wird. Eine solche Weitergabe erfordert im Allgemeinen das Schreiben von std::move (es sei denn, Modus 2 wird verwendet), aber beachten Sie, dass dies nur eine Umwandlung ist, die nichts tut (insbesondere keine Dereferenzierung), so dass keine Kosten entstehen.

    • Sollte es vorstellbar sein, dass irgendetwas eine Ausnahme zwischen dem Beginn des functionsaufrufs und dem Punkt, an dem es (oder ein enthaltener Aufruf) das angegebene Objekt in eine andere Datenstruktur verschiebt, austriggers (und diese Ausnahme nicht bereits in der function selbst gefangen ist) ), dann wird bei Verwendung von Modus 1 das Objekt, auf das der intelligente pointers verweist, zerstört, bevor eine catch Klausel die Ausnahme behandeln kann (weil der functionsparameter während des Abwickelns des Stapels zerstört wurde), nicht jedoch bei Verwendung von Modus 3. Letzteres ergibt Der Aufrufer hat in solchen Fällen die Möglichkeit, die Daten des Objekts wiederherzustellen (durch Abfangen der Ausnahme). Beachten Sie, dass Modus 1 hier keinen Speicherverlust verursacht , aber zu einem nicht wiederherstellbaren Datenverlust für das Programm führen kann, was ebenfalls unerwünscht sein kann.

    Einen intelligenten pointers zurückgeben: immer nach Wert

    Um ein Wort über das Zurückgeben eines intelligenten pointerss zu schließen, der vermutlich auf ein Objekt verweist, das für den Aufrufer erstellt wird. Dies ist nicht wirklich ein Fall, der mit der Übergabe von pointersn in functionen vergleichbar ist, aber der Vollständigkeit halber möchte ich darauf bestehen, dass in solchen Fällen immer Wert zurückgegeben wird (und nicht std::move in der return statement). Niemand möchte einen Verweis auf einen pointers erhalten, der wahrscheinlich gerade nicht zusammengefügt wurde.

    Ja, Sie müssen, wenn Sie das unique_ptr nach Wert im Konstruktor nehmen. Einfachheit ist eine schöne Sache. Da unique_ptr kopierbar ist (Privatkopie ctor), sollte das, was Sie geschrieben haben, Ihnen einen Compilererrors geben.

    Edit: Diese Antwort ist falsch, obwohl, streng genommen, der Code funktioniert. Ich verlasse es nur hier, weil die Diskussion darunter zu nützlich ist. Diese andere Antwort ist die beste Antwort zum Zeitpunkt, als ich das letzte Mal bearbeitet habe: Wie übergebe ich ein unique_ptr Argument an einen Konstruktor oder eine function?

    Die Grundidee von ::std::move besteht darin, dass Personen, die Ihnen das unique_ptr sollten, um das Wissen auszudrücken, dass sie wissen, dass die von ihnen übergebenen unique_ptr die Eigentumsrechte verlieren.

    Das bedeutet, dass Sie in Ihren Methoden eine rvalue-Referenz auf ein unique_ptr verwenden sollten, nicht ein unique_ptr selbst. Dies wird sowieso nicht funktionieren, da die Übergabe in einem einfachen alten unique_ptr eine Kopie erfordern würde, und das ist explizit in der Schnittstelle für unique_ptr verboten. Interessanterweise wird durch die Verwendung einer benannten rvalue-Referenz diese wieder in einen Lvalue umgewandelt, so dass Sie auch ::std::move innerhalb Ihrer Methoden verwenden müssen.

    Das bedeutet, dass Ihre zwei Methoden so aussehen sollten:

     Base(Base::UPtr &&n) : next(::std::move(n)) {} // Spaces for readability void setNext(Base::UPtr &&n) { next = ::std::move(n); } 

    Dann würden Leute, die die Methoden verwenden, dies tun:

     Base::UPtr objptr{ new Base; } Base::UPtr objptr2{ new Base; } Base fred(::std::move(objptr)); // objptr now loses ownership fred.setNext(::std::move(objptr2)); // objptr2 now loses ownership 

    Wie Sie sehen, drückt das ::std::move aus, dass der pointers Besitz an dem Punkt verliert, an dem er am relevantesten und hilfreichsten ist. Wenn dies unsichtbar geschehen würde, wäre es sehr verwirrend für Leute, die Ihre class benutzen, objptr plötzlich den Besitz verliert, ohne dass dies offensichtlich ist.

     Base(Base::UPtr n):next(std::move(n)) {} 

    sollte viel besser sein als

     Base(Base::UPtr&& n):next(std::forward(n)) {} 

    und

     void setNext(Base::UPtr n) 

    sollte sein

     void setNext(Base::UPtr&& n) 

    mit demselben Körper.

    Und … was ist evt in handle() ??

    Nach oben abgestimmt Antwort. Ich ziehe es vor, rValue-Referenz zu übergeben.

    Ich verstehe, was das Problem bei der Übergabe von Rvalue Referenz verursacht. Aber teilen wir dieses Problem auf zwei Seiten:

    • für den Anrufer:

    Ich muss Code Base newBase(std::move()) schreiben Base newBase(std::move()) oder Base newBase() .

    • für Angerufene:

    Library author should guarantee it will actually move the unique_ptr to initialize member if it want own the ownership.

    Das ist alles.

    If you pass by rvalue reference, it will only invoke one “move” instruction, but if pass by value, it’s two.

    Yep, if library author is not expert about this, he may not move unique_ptr to initialize member, but it’s the problem of author, not you. Whatever it pass by value or rvalue reference, your code is same!

    If you are writing a library, now you know you should guarantee it, so just do it, passing by rvalue reference is a better choice than value. Client who use you library will just write same code.

    Now, for your question. How do I pass a unique_ptr argument to a constructor or a function?

    You know what’s the best choice.

    http://scottmeyers.blogspot.com/2014/07/should-move-only-types-ever-be-passed.html