2017-09-28 4 views
2

Können sagen, wir die folgende Klasse:Sollte der rvalue nach einem Zug gültig sein?

class Foo 
{ 
public: 
    Foo() { _bar=new Bar }; 
    Foo(const Foo &right) { _bar=new Bar(right.bar); }; 
    Foo(Foo &&right) { _bar=right._bar; right.bar=new Bar(); }; 

    ~Foo() { delete _bar; } 

    Foo &operator=(const Foo &right) { _bar->opertor=(right.bar); return *this;} 
    Foo &operator=(Foo &&right) { std::swap(_bar, right._bar); return *this;} 

    void func() { _bar->test=1 }; 

private: 
    Bar *_bar; 
}; 

ist es legitim, an folgenden zu ändern und die Endbenutzer zu erwarten zu wissen, dass nach dem Umzug, dass der R-Wert ist nicht mehr gültig durchgeführt wird (in dieser Berufung alles andere als der Zuweisungsoperator konnte abstürzen)?

mein Anliegen kommt von der Tatsache, dass func (und alle anderen Funktionen in der Klasse) davon ausgehen, dass _bar existiert.

+0

Fehler behoben und Null-Checks zum Operator = – Krazer

+0

Die Bearbeitung ändert im Wesentlichen die Frage. Zuvor würde es einen Null-Zeiger dereferenzieren, wenn sie 'operator =' nach dem Verschieben verwenden würden; wir dachten, du fragst, ob der Umzug in Ordnung ist und der Anrufcode sollte es wissen, um das zu vermeiden. Aber jetzt ist das Objekt nach der Bewegung gültig und es ist unklar, was Ihre Frage ist (in beiden Fällen ist das Objekt jetzt gültig). Bitte bearbeiten Sie noch einmal, um Ihre Absicht zu verdeutlichen –

+0

Ich habe es vielleicht falsch gefragt, aber für mich ist die Frage die gleiche, mit dem folgenden Code-Schnipsel 'Foo a, b; a = std :: move (b); b.func(); '. die Top-Implementierung wird nicht abstürzen, Verhalten ist wahrscheinlich unerwünscht, aber es wird nicht abstürzen. Unter der neuen Implementierung ruft b.func() nach dem Verschieben einen Absturz auf.Die Frage ist dann legitim zu erwarten, dass der Benutzer wissen sollte, dass der Aufruf von etwas anderem als operator = auf b, nach dem Umzug, zum Absturz bringen könnte? – Krazer

Antwort

7

Im Prinzip kann es ungültig werden, obwohl Sie vielleicht erwägen, es in einem zuweisbaren Zustand zu belassen (was Ihre ursprüngliche Implementierung, also bearbeitet, nicht getan hat). Dies würde der Richtlinie der Standardbibliothek folgen:

Sofern nicht anders angegeben, werden alle Standardbibliotheksobjekte, aus denen verschoben wurde, in einen gültigen, jedoch nicht angegebenen Status versetzt. Das heißt, nur die Funktionen, ohne Vorbedingungen, wie der Zuweisungsoperator, kann sicher auf dem Objekt verwendet werden, nachdem es von

verschoben wurde ich würde empfehlen, den Zuweisungsoperator Neuimplementierung, so dass es Swaps „dieses“ Objekt mit ein neu gebauter. Dies ist im Allgemeinen ein guter Weg, um falsches Verhalten beim Implementieren von Zuweisungen zu vermeiden.

+0

was ist mit der Änderung es nur zu diesem 'Foo & Operator = (const Foo & andere) {if (_bar == nullptr) _bar = new Bar(); _bar-> opertor = (rechts.bar); return * this;} 'und' Foo & operator = (Foo && andere) {if (_bar! = nullptr) delete _bar; _bar = rechts._bar; right._bar = nullptr; return * this;} ' – Krazer

+0

Sie könnten das tun, wenn Sie unnötige Zuweisungen in Zuweisungen vermeiden wollen, um' Foo'-Objekte zu leeren, aber haben Sie bestätigt, dass das keine vorzeitige Optimierung ist? Und wenn das der Fall ist, könnten Sie stattdessen in Erwägung ziehen, dass der Standardkonstruktor nicht "neue Bar" hat, was der einzige Fall ist, in dem ein "überflüssiges" "Bar" -Objekt normalerweise zugewiesen würde. Es hängt von Ihrem Anwendungsfall für diesen Kurs ab. –

+0

Ich denke, der Standardkonstruktor muss die Zuweisung haben, da keine der Klassenfunktionen erwarten, dass _bar ein nullptr ist. Ohne den Zuweisungsbenutzer, der 'Foo foo;' erstellt und dann 'foo.func();' aufgerufen hat, stürzt der Benutzer ab. Dies ist weniger eine Optimierung als für den Code, um das zu tun, was "erwartet" ist. Nehmen wir an, Bar ist 20MB und ich erstelle 'Foo a, b;' Ich würde erwarten, dass 40MB + später verwendet wird, wenn ich 'a = b;' setze, dass nur 20MB verwendet werden, da b jetzt leer sein sollte. Der obere Fall wird das nicht tun. – Krazer

0

ist es legitim, es in den folgenden zu ändern und zu erwarten, dass der Endbenutzer weiß, dass nach dem Verschieben der rvalue nicht mehr gültig ist?

Sie sollten das sicherlich tun. Eine rvalue-Referenz soll nicht in einem nutzbaren Zustand gehalten werden. Die Idee hinter den Konstruktoren move und move ist, dass Sie die die nützlichen Daten aus dem Objekt verschieben. Ich würde nicht verwenden nicht mehr gültige um es zu beschreiben. Es ist immer noch ein gültiges Objekt aus C++ Sicht, genau wie nullptr ist ein gültiger Zeiger.

0

Die zweite Version ist idiomatischer. Nachdem ein Objekt verschoben wurde, wird davon ausgegangen, dass es nicht mehr verwendet wird.

Die Überprüfung auf nullptr vor dem Aufruf delete auf den Zeiger ist unnötig, da es in der Norm definiert ist, nichts zu tun.

Wenn der Benutzer das verschobene Objekt verwenden möchte, ist es seine Aufgabe, sicherzustellen, dass es in einem gültigen Zustand ist (d.h. Zuweisung von einem gültigen Objekt).

6

Ein verschobenes Objekt soll in einem gültigen aber nicht spezifizierten Zustand sein. Beachten Sie, dass dies eine Empfehlung, aber keine absolute Anforderung des Standards ist.

Ihr zweiter Code bricht ab, wenn danach ein normaler Vorgang ausgeführt wird (speziell der Kopierzuweisungsoperator).

Wenn _bar == nullptr ein gültiger Status ist, dann ist Ihr Kopierzuweisungs-Operator abgehört; Wenn es kein gültiger Status ist, würde ich sagen, dass Ihr Move-Konstruktor abgehört wurde.

NB.Im zweiten Code ist der if Check-in im Destruktor überflüssig, da es zulässig ist, einen Nullzeiger zu löschen.

+0

Hinweis: Die Frage wurde bearbeitet, seit diese Antwort veröffentlicht wurde - bitte überprüfen Sie den ursprünglichen Bearbeitungsverlauf –

0

Sie sollten für Ihre Foo dokumentieren, welche Voraussetzungen jedes Mitglied Funktion hat, zum Beispiel:

void Foo::func(); 

Benötigt:*this ist nicht in einem Zustand von-bewegt.

Dann sind Sie gut für alle Ihre Kunden zu gehen, die Foo::func() verwenden müssen, es sei denn ...

Wenn Sie Foo mit jemand anderem Bibliothek verwenden, die func() erfordert, und dokumentiert nicht etwas zusammen die Zeilen von "außer wenn in einem verschobenen Zustand", dann hast du kein Glück.

Zum Beispiel: Nehmen wir an, Ihr Foo ein operator< so hat:

bool operator<(const Foo& x, const Foo& y); 

requries: Weder x noch y in einem eingefahrenen Zustand von sein kann.

Nun, wenn Sie so etwas wie:

std::vector<Foo> v; 
// ... fill v 
std::sort(v.begin(), v.end()); // oops! 

Die letzte Zeile oben Foo erfordert LessThanComparable, ob oder nicht Foo zu sein, ist in einem eingefahrenen Zustand von. Und das gilt für alle des generischen Codes in der Std :: Lib. Die requires-Klauseln für den std-Code gelten für vom Benutzer bereitgestellten Code und machen keine Ausnahmen für verschobene Objekte.

Wenn Ihr operator< jedoch funktioniert, wenn sich eines der Argumente im Status "Moved-from" befindet, ist der Aufruf an std::sort in Ordnung. Dies gilt auch dann, wenn func() immer noch die Anforderung hat, dass Foonicht in einem verschobenen Zustand sein soll. Dies liegt daran, std::sort erfordert func() überhaupt nicht zu arbeiten.

Also zusammenfassend hängt alles davon ab, welchen anderen Code Ihr Foo interagiert. Vielleicht ist es ok und vielleicht nicht. Dokumentieren Sie, was Foo tut, und verstehen Sie die Anforderungen des von Ihnen verwendeten Codes Foo mit.

Verwandte Themen