2015-11-20 16 views
16

Seit 2011 haben wir sowohl kopieren und verschieben Zuordnung. this answer argumentiert jedoch ziemlich überzeugend, dass für Ressourcenverwaltungsklassen nur ein Zuweisungsoperator benötigt wird. Für std::vector zum Beispiel würde dies aussehenWarum hat std :: vector zwei Zuweisungsoperatoren?

vector& vector::operator=(vector other) 
{ 
    swap(other); 
    return*this; 
} 

Der wichtige Punkt hier ist, dass das Argument von Wert genommen wird. Dies bedeutet, dass in dem Moment, in dem der eigentliche Funktionskörper eingegeben wird, ein Großteil der Arbeit bereits durch die Konstruktion von other erledigt wurde (wenn möglich durch move-Konstruktor, ansonsten durch Kopierkonstruktor). Daher implementiert dies sowohl die Kopier- als auch die Bewegungszuweisung automatisch korrekt.

Wenn dies richtig ist, warum wird (nach this documentation at least) std::vectornicht auf diese Weise umgesetzt?


bearbeiten zu erklären, wie das funktioniert. Überlegen Sie, was in den obigen Code in den folgenden Beispielen zu other geschieht

void foo(std::vector<bar> &&x) 
{ 
    auto y=x;    // other is copy constructed 
    auto z=std::move(x); // other is move constructed, no copy is ever made. 
    // ... 
} 
+0

I habe mich auch darüber gewundert. Kopieren und Tauschen scheint ziemlich genial. Vielleicht liegt es an alten Gründen, die Funktionssignatur genau gleich zu halten? Ich bin gespannt auf eine gute Antwort. – MicroVirus

+1

Dies erfordert eine Speicherzuweisung. Das Kopieren von Inhalten aus einem L-Wert kann nicht erfolgen, wenn der Zessionar genügend Kapazität hat. – juanchopanza

+0

@juanchopanza Ich dachte auch, dass. Wenn das so ist, dann ist die nette Antwort von GManNickG bei Locks nicht so schön? – Walter

Antwort

-2

Eigentlich gibt es drei Zuweisungsoperatoren definiert:

vector& operator=(const vector& other); 
vector& operator=(vector&& other); 
vector& operator=(std::initializer_list<T> ilist); 

Ihr Vorschlag vector& vector::operator=(vector other) verwendet die Kopie und Swap-Idiom. Das heißt, wenn der Operator aufgerufen wird, wird der ursprüngliche Vektor in den Parameter kopiert und jedes einzelne Element im Vektor kopiert. Dann wird diese Kopie mit this ausgetauscht. Der Compiler kann diese Kopie möglicherweise entfernen, aber diese Kopie ist optional, die Semantik des Verschiebens ist Standard.

können Sie das Idiom verwenden, um den Kopierzuweisungsoperator zu ersetzen:

vector& operator=(const vector& other) { 
    swap(vector{other}); // create temporary copy and swap 
    return *this; 
} 

Jedes Mal, wenn eines der Elemente Kopieren Würfe, wird diese Funktion auch werfen.

Um die Bewegungszuweisungsoperator auslassen nur das Kopieren zu implementieren:

vector& operator=(vector&& other) { 
    swap(other); 
    return *this; 
} 

Da swap() nie nicht wirft, wird weder die Bewegung Zuweisungsoperator. die Betreiber bewegen Zuordnung

vector& operator=(std::initializer_list<T> ilist) { 
    return *this = vector{ilist}; 
} 

Wir benutzten:

Die initializer_list -assignment auch mit dem Umzug Zuweisungsoperator und eine anonyme temporäre leicht implementiert werden kann. Als Konsequenz wird die initializer_list Zuweisung operatpr nur dann geworfen, wenn eine der Elementinstanziierungen ausgelöst wird.

Wie bereits erwähnt, könnte der Compiler die Kopie für die Kopierzuordnung freigeben. Der Compiler ist jedoch nicht verpflichtet, diese Optimierung zu implementieren. Es ist verpflichtet, eine Bewegungssemantik zu implementieren.

+0

Haben Sie tatsächlich die Antwort gelesen, auf die in meinem Beitrag verwiesen wird? Der Punkt ist, dass Ihre beiden Implementierungen der Zuweisungs- und Kopierzuweisung perfekt und identisch durch die Implementierung * one * in meiner Frage implementiert werden. – Walter

+0

@Walter das ist nur wahr, wenn der Compiler die Elision implementiert. Der Standard bietet dem Compiler-Anbieter alle Freiheiten, dies zu tun, erfordert dies aber nicht. – cdonat

+0

Sie sagen also, dass, wenn eine Funktion, die ein Argument nach Wert nimmt, mit einer R-Wert-Referenz aufgerufen wird, es zulässig ist, keine Verschiebung vorzunehmen, sondern stattdessen zu kopieren (um das Funktionsargument zu initialisieren)? – Walter

7

Wenn der Elementtyp nothrow kopierbar ist, oder der Behälter berücksichtigt nicht die starke Ausnahme Garantie, dann eine Kopie-Zuweisungsoperator kann Zuordnung in dem Fall vermeiden, wo das Zielobjekt eine ausreichende Kapazität hat:

vector& operator=(vector const& src) 
{ 
    clear(); 
    reserve(src.size()); // no allocation if capacity() >= src.size() 
    uninitialized_copy_n(src.data(), src.size(), dst.data()); 
    m_size = src.size(); 
} 
+0

Okay, das ist ein faires Argument (und wurde bereits in den Kommentaren behandelt). Es scheint in der Tat so, dass der C++ - Standard keine Ausnahme für die Kopierzuweisung garantiert, aber von C++ 17 möglicherweise für die Zugzuweisung (Allokator erlaubt). – Walter