2014-03-01 10 views
8
//code from https://skillsmatter.com/skillscasts/2188-move-semanticsperfect-forwarding-and-rvalue-references 
class Widget { 
public: 
    Widget(Widget&& rhs) 
     : pds(rhs.pds) // take source’s value 
    { 
     rhs.pds = nullptr; // why?? 
    } 

private: 
    struct DataStructure; 
    DataStructure *pds; 
}; 

Ich kann den Grund für die Einstellung rhd.pds zu nullptr nicht verstehen.Warum müssen wir den rvalue-Verweis auf null im Move-Konstruktor setzen?

Was passiert, wenn wir diese Zeile entfernen: rhs.pds = nullptr;

+3

Gibt es eine destructor Sie, dass der Zeiger zu frei hätte entfernt versucht, wenn es wasn Bin nicht geklärt? – user2357112

+0

so dass keine zwei Objekte Mitglieder auf die gleichen Speicherorte zeigen ... – HadeS

+0

Der Link erfordert eine kostenpflichtige Anmeldung. :((Es klingt auch wie ein "Skill smatter" ist ein Prozess, der Ihnen ein "Überangebot an Fähigkeiten" geben würde.) – Potatoswatter

Antwort

12

Einige Details der Klasse wurden entfernt. Insbesondere weist der Konstruktor das DataStructure-Objekt dynamisch zu, und der Destruktor hebt es auf. Wenn Sie während einer Verschiebung gerade den Zeiger von einem Widget zu einem anderen kopiert haben, hätten beide Widget s Zeiger auf dasselbe zugewiesene DataStructure Objekt. Dann, wenn diese Objekte zerstört werden, würden sie beide versuchen, delete es. Dies würde zu undefiniertem Verhalten führen. Um dies zu vermeiden, muss der interne Zeiger des Widget, von dem verschoben wird, auf nullptr gesetzt werden.

Dies ist ein Standardmuster bei der Implementierung eines Move-Konstruktors. Sie möchten das Eigentumsrecht für einige dynamisch zugewiesene Objekte von einem Objekt auf ein anderes übertragen. Daher müssen Sie sicherstellen, dass das ursprüngliche Objekt nicht mehr die zugeordneten Objekte besitzt.

zeigt schematisch, beginnen Sie mit dieser Situation aus, das Eigentum an der DataStructure von einem Widget zum anderen bewegen wollen:

┌────────┐  ┌────────┐ 
    │ Widget │  │ Widget │ 
    └───╂────┘  └────────┘ 
     ┃ 
     ▼ 
┌───────────────┐ 
│ DataStructure │ 
└───────────────┘ 

Wenn Sie den Mauszeiger einfach kopiert, dann würden Sie haben:

┌────────┐  ┌────────┐ 
    │ Widget │  │ Widget │ 
    └───╂────┘  └───╂────┘ 
     ┗━━━━━━━━┳━━━━━━━┛ 
        ▼ 
     ┌───────────────┐ 
     │ DataStructure │ 
     └───────────────┘ 

Wenn Sie dann den ursprünglichen Widget Zeiger auf nullptr gesetzt, Sie haben:

┌────────┐   ┌────────┐ 
    │ Widget │   │ Widget │ 
    └────────┘   └───╂────┘ 
          ┃ 
          ▼ 
        ┌───────────────┐ 
        │ DataStructure │ 
        └───────────────┘ 

Das Eigentumsrecht wurde erfolgreich übertragen und beide Widget s können zerstört werden, ohne ein undefiniertes Verhalten zu verursachen.

+0

so frage ich mich, dass für diese Situation gibt es einen Unterschied zwischen den Ausdrücken' rhs.pds = nullptr; 'und' pds = nullptr 'Ich denke, ich kann' pds = nullptr' anstelle von 'rhs.pds = nullptr;' schreiben. Kann ich? – askque

2

Das DataStructure Objekt mit hoher Wahrscheinlichkeit durch den Widget „owned“, und verhindert, dass der Zeiger das Zurücksetzen von ihm versehentlich gelöscht werden, wenn die Widget zerstört wird.

Alternativ ist es üblich, Objekte in einen "leeren" oder "default" -Zustand zurückzusetzen, wenn sie verschoben werden, und das Zurücksetzen des Zeigers ist ein harmloser Weg, der Konvention zu folgen.

1
class Widget { 
    public: 
    Widget(Widget&& rhs) 
     : pds(rhs.pds) // take source’s value 
    { 
     rhs.pds = nullptr; // why?? 
    } 
    ~Widget() {delete pds}; // <== added this line 

private: 
    struct DataStructure; 
    DataStructure *pds; 
}; 

Ich habe einen Destruktor in der oben genannten Klasse hinzugefügt.

Um zu veranschaulichen, was passieren würde, wenn Sie die Nullptr-Zuweisung entfernen, überprüfen Sie die oben genannten Methoden. Ein Widget a würde in einer Hilfsfunktion erstellt und dem Widget b zugewiesen werden.

Da Widget a außerhalb des Geltungsbereichs seines Destruktors ist, wird der Speicher freigegeben, und Sie bleiben mit Widget b zurück, das auf ungültige Speicheradresse verweist.

Wenn Sie nullptr zu rhs zuordnen, wird eine destructor auch genannt wird, aber da löschen nullptr tut nichts ist alles gut :)

+2

Warum std :: move (a) zurückgeben?! – Omid

+1

@omid Um die Rückgabewertoptimierung zu verhindern: -) – juanchopanza

+1

@Blaz, wie omid und juanchopanza zeigen 'return move (a);' ist in der Regel eine schlechte Sache. Der Compiler (normalerweise) behandelt den Rückgabewert implizit als einen R-Wert. Du gewinnst also nichts von 'move'. Aber, noch wichtiger, es gibt eine weitere Optimierung, die möglich ist (RVO genannt, schneller als Verschieben), und diese Optimierung wird deaktiviert, wenn Sie eine explizite "Bewegung" ausführen. Es gibt Fälle, in denen es Sinn macht, aber wenn Sie es blind tun, werden Sie die Dinge langsamer verlangsamen als Sie beschleunigen. –

Verwandte Themen