2009-11-14 13 views
70

Da ein CopykonstruktorKopierkonstruktor und Operatorüberladung in C++: Ist eine gemeinsame Funktion möglich?

MyClass(const MyClass&); 

und einem Operator = Überlastung

MyClass& operator = (const MyClass&); 

so ziemlich den gleichen Code haben, den gleichen Parameter, und nur auf die Rückkehr unterscheiden, ist es möglich, eine gemeinsam zu haben Funktion für beide zu benutzen?

+3

"... haben fast den gleichen Code ..."? Hmm ... Du musst etwas falsch machen. Versuchen Sie, die Notwendigkeit zu minimieren, benutzerdefinierte Funktionen dafür zu verwenden, und lassen Sie den Compiler die ganze schmutzige Arbeit erledigen. Dies bedeutet häufig, Ressourcen in einem eigenen Mitgliedsobjekt zu kapseln. Sie könnten uns Code zeigen. Vielleicht haben wir ein paar gute Designvorschläge. – sellibitze

+1

Mögliches Duplikat von [Codevervielfältigung zwischen operator = und dem Kopierkonstruktor reduzieren] (http://stackoverflow.com/questions/1477145/reducing-code-duplication-between-operator-and-the-copy-constructor) – mpromonet

Antwort

96

Ja. Es gibt zwei gebräuchliche Optionen. One - was ich nicht empfehlen - ist die operator= vom Copykonstruktor explizit zu nennen:

MyClass(const MyClass& other) 
{ 
    operator=(other); 
} 

Allerdings bietet eine gutes operator= ist eine Herausforderung, wenn es darum geht, mit dem alten Zustand und Probleme im Umgang von selbst ergeben, Zuordnung. Außerdem werden alle Mitglieder und Basen standardmäßig initialisiert, auch wenn sie von other zugewiesen werden sollen. Dies kann nicht einmal für alle Mitglieder und Basen gelten und selbst wenn es gültig ist, ist es semantisch redundant und kann praktisch teuer sein.

Eine immer beliebtere Lösung ist die Implementierung von operator= mit dem Kopierkonstruktor und einer Swap-Methode.

MyClass& operator=(const MyClass& other) 
{ 
    MyClass tmp(other); 
    swap(tmp); 
    return *this; 
} 

oder sogar:

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

A swap Funktion typischerweise einfach zu schreiben, da es nur das Eigentum an den Einbauten Swaps und keinen bestehenden Zustand zu bereinigen oder neue Ressourcen zuweisen.

Die Vorteile des Kopier- und Swap-Idioms sind, dass es automatisch selbszugewiesen wird und - sofern der Tauschvorgang kein No-Throw ist - auch sehr sicher ist.

Um stark ausnahmesicher zu sein, muss ein 'Hand' geschriebener Zuweisungsoperator in der Regel eine Kopie der neuen Ressourcen zuweisen, bevor die alten Ressourcen des Zuweisungsnehmers freigegeben werden, so dass der alte Zustand bei einer Ausnahme die neuen Ressourcen zuweisen kann immer noch zurückgegeben werden. All dies ist kostenlos mit Copy-and-Swap, ist aber in der Regel komplexer und damit fehleranfällig.

Die eine Sache, auf die Sie achten sollten, ist sicherzustellen, dass die Swap-Methode ein echter Swap ist und nicht der Standardwert std::swap, der den Kopierkonstruktor und den Zuweisungsoperator selbst verwendet.

In der Regel wird ein memberwise swap verwendet. std::swap funktioniert und ist 'No-Throw' garantiert mit allen grundlegenden Typen und Zeigertypen. Die meisten Smart-Pointer können auch mit einer No-Throw-Garantie getauscht werden.

+3

Sie sind keine gewöhnlichen Operationen. Während der copy ctor zum ersten Mal die Mitglieder des Objekts initialisiert, überschreibt der Zuweisungsoperator vorhandene Werte. Wenn man bedenkt, ist alling 'operator =' aus dem copy ctor in der Tat ziemlich schlecht, weil es zunächst alle Werte auf einen Standardwert initialisiert, nur um sie anschließend mit den Werten des anderen Objekts zu überschreiben. – sbi

+0

Ich sagte gemeinsame Optionen, nicht Operationen. Ich stimme völlig zu, dass das Aufrufen von 'operator =' von einem Kopierkonstruktor nicht gut ist, aber man muss sich nur einen vernünftigen Blick auf den Code der realen Welt werfen, um zu sehen, wie häufig er ist. –

+0

Downvoters, kümmern sich um zu erklären? –

11

Der Kopierkonstruktor führt die erstmalige Initialisierung von Objekten durch, die zuvor als Raw-Speicher verwendet wurden. Der Zuweisungsoperator OTOH überschreibt vorhandene Werte durch neue. Dies bedeutet häufiger als nie zuvor, dass alte Ressourcen (z. B. Speicher) gelöscht und neue zugewiesen werden.

Wenn es eine Ähnlichkeit zwischen den beiden gibt, ist es, dass der Zuweisungsoperator Zerstörung und Kopierkonstruktion ausführt. Einige Entwickler haben die Zuweisung tatsächlich durch In-Place-Destruction gefolgt von Placement Copy-Construction implementiert. Dies ist jedoch eine sehr schlechte Idee.(Was passiert, wenn dies ist der Zuweisungsoperator einer Basisklasse, die bei der Zuordnung von einer abgeleiteten Klasse genannt?)

Was in der Regel die kanonische Idiom heutzutage betrachtet hat wird swap als Charles mit vorgeschlagen:

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

Diese nutzt Kopie -Konstruktion (beachten Sie, dass other kopiert wird) und Destruction (es wird am Ende der Funktion zerstört) - und es verwendet sie auch in der richtigen Reihenfolge: Konstruktion (könnte fehlschlagen) vor der Zerstörung (darf nicht fehlschlagen).

+0

Sollte 'swap'' virtuell' erklärt werden? – nonplus

+1

@Johannes: Virtuelle Funktionen werden in polymorphen Klassenhierarchien verwendet. Zuweisungsoperatoren werden für Werttypen verwendet. Die beiden mischen sich kaum. – sbi

-2

Etwas stört mich über:

MyClass& operator=(const MyClass& other) 
{ 
    MyClass tmp(other); 
    swap(tmp); 
    return *this; 
} 

Zuerst liest das Wort „swap“ wenn mein Geist „Kopie“ ärgert meinen gesunden Menschenverstand denkt. Außerdem hinterfrage ich das Ziel dieses ausgefallenen Tricks. Ja, alle Ausnahmen beim Erstellen der neuen (kopierten) Ressourcen sollten vor dem Swap erfolgen. Dies scheint ein sicherer Weg zu sein, um sicherzustellen, dass alle neuen Daten gefüllt sind, bevor sie in Betrieb gehen.

Das ist in Ordnung. Also, was ist mit Ausnahmen, die nach dem Swap passieren? (wenn die alten Ressourcen zerstört werden, wenn das temporäre Objekt den Gültigkeitsbereich verlässt) Aus der Perspektive des Benutzers der Zuweisung ist die Operation fehlgeschlagen, außer dies ist nicht der Fall. Es hat einen großen Nebeneffekt: Die Kopie ist tatsächlich passiert. Es war nur eine Ressourcenbereinigung, die fehlgeschlagen ist. Der Status des Zielobjekts wurde geändert, obwohl die Operation von außen als fehlgeschlagen erscheint.

So schlage ich vor, anstelle von „Swap“ einen natürlicheres „Transfer“ zu tun:

MyClass& operator=(const MyClass& other) 
{ 
    MyClass tmp(other); 
    transfer(tmp); 
    return *this; 
} 

Es ist immer noch der Bau des temporären Objekts, aber die nächsten sofortigen Maßnahmen ist es, alle aktuellen Ressourcen zu befreien das Ziel vor dem Verschieben (und NULL, so dass sie nicht doppelt freigegeben werden) die Ressourcen der Quelle zu ihm.

Anstelle von {construct, move, destruct} schlage ich {construct, destruct, move} vor. Der Zug, der die gefährlichste Aktion ist, ist der letzte, der nach der Regelung des Restes unternommen wurde.

Ja, Zerstörung Fehler ist ein Problem in jedem Schema. Die Daten sind entweder beschädigt (kopiert, wenn Sie nicht dachten, dass es war) oder verloren (befreit, wenn Sie nicht dachten, dass es war). Verloren ist besser als verdorben. Keine Daten sind besser als schlechte Daten.

Transfer statt Tausch. Das ist sowieso mein Vorschlag.

+2

Ein Destruktor darf nicht fehlschlagen, so dass Ausnahmen bei der Zerstörung nicht erwartet werden. Und ich bekomme nicht, was wäre der Vorteil der Bewegung hinter der Zerstörung zu bewegen, wenn Bewegung die gefährlichste Operation ist? Das heißt, in dem Standardschema wird ein Bewegungsfehler den alten Zustand nicht korrumpieren, während dein neues Schema dies tut. Warum also? "Zuerst, liest das Wort" swap ", wenn mein Verstand denkt" Kopie "irritiert" -> Als Bibliotheksautor, Sie kennen in der Regel gängige Praktiken (Kopieren + Tauschen), und die Crux ist "meine Meinung". Dein Geist ist tatsächlich hinter der öffentlichen Schnittstelle verborgen. Darum geht es bei wiederverwendbarem Code. –

Verwandte Themen