Der Zuweisungsoperator - Copy Assignment

C++ Operatoren überladen

Der Ausdruck int i; i = 1; verwendet den Zuweisungsoperator =, um i den Wert 1 zuzuweisen. Im Fall von Klassen spricht man bei zwei Objekten vom selben Typ von Kopier-Zuweisung (Kopie-Zuweisung, engl. copy assignment), wobei auch das Zeichen = benutzt wird. Eine Ausnahme tritt bei der Initialisierung eines Objekts mittels = auf. Statt zunächst den Standardkonstruktor aufzurufen und anschließend die Zuweisung auszuführen, wird direkt der Kopierkonstruktor aufgerufen.

T a;     
T b = a;  // Kopierkonstruktor
b = a;    // Zuweisung

Der Zuweisungsoperator übernimmt als Argument ein Objekt bzw. eine Referenz auf ein Objekt gleichen Typs und sollte eine Referenz auf sich selber zurückgeben, um verkettete Zuweisungen der Art a = b = c; zu ermöglichen.

Beispiel

Das folgende Beispiel zeigt eine sehr einfache Implementierung des Zuweisungsoperator der Klasse bogus.

#include <string>
#include "A.hpp"

class bogus
{
public:
    bogus& operator= (const bogus& other)
    {
        id_ = other.id_;
        s_ = other.s_;
        a_ = other.a_;
        return *this;
    }
private:
    int id_;
    std::string s_;
    A a_;
};

Um im Fall von Selbstzuweisung unnötige Kopien zu vermeiden kann am Anfang des Zuweisungsoperators die Zeile

if (this == &other) return *this;

eingefügt werden. Ein schwierigeres Problem stellen Ausnahmen während der Zuweisung dar. Wirft bspw. die Zuweisung von s_ eine Ausnahme, so liegt das Objekt anschließend in einem für den Benutzer wahrscheinlich unbrauchbaren Zustand vor, da sich die ID (id_) schon geändert hat, die restlichen Daten jedoch noch dem alten Zustand entsprechen. Bei Maßnahmen, die das Verhalten eines Objekts im Fall von Ausnahmen definieren und sicherstellen spricht man von exception safety. Um dem Zuweisungsoperator von bogus die sog. "Starke Garantie" (Strong Guarantee: Zustand des Programms vor der Ausnahme == Zustand nach der Ausnahme) zu geben, müssen einige Maßnahmen getroffen werden. Dazu wird das "Create Temporary and Swap" Idiom angewendet.

class bogus
{
public:
    bogus& operator= (const bogus& other)
    {
        bogus temp(other); // Kopierkonstruktor
        temp.swap(*this);
        return *this;
    }

    void swap (bogus & other) // throw ()
    {
        std::swap (id_, other.id_);
        s_.swap(other.s_);
        a_.swap(other.a_);
    }
private:
    int id_;
    std::string s_;
    A a_;
};

Bei dieser Lösung wird die ganze Kopierarbeit im Kopierkonstruktor eines temporären Objekts durchgeführt. Wenn dieser Vorgang erfolgreich war, werden die Daten des temporären Objekt durch die Funktion swap ausgetauscht. Damit der Zuweisungsopertor die Strong Guarantee bieten kann, muss swap die sog. Nothrow Guarantee bieten, d.h. dass sie niemals eine Ausnahme wirft. Um dies sicherzustellen, muss in diesem Beispiel die Klasse A ebenfalls eine solche swap Funktion mit Nothrow Guarantee anbieten. Wenn das nicht der Fall ist und die Zuweisung von A Ausnahmen werfen kann, müssen weitere Anpassungen vorgenommen werden. Dazu wird das Pimpl-Idiom (Herb Sutter[1]) angewendet.

class bogus
{
public:
    bogus();
    bogus(bogus&);
    bogus& operator= (const bogus& other);
    void swap (bogus & other);

private:
    struct impl;
    typedef std::tr1::shared_ptr<impl> pimpl;
    pimpl pimpl_;
};


// bogus.cpp
struct bogus::impl
{
    int id_;
    std::string s_;
    A a_;
};

bogus::bogus()
: pimpl_(new impl)
{}

bogus::bogus(bogus& other)
: pimpl_(new impl(other.pimpl_))
{}

bogus& bogus::operator= (const bogus& other)
{
    bogus temp(other); // Kopierkonstruktor
    temp.swap(*this);
    return *this;
}

void bogus::swap(bogus & other)
{
    pimpl_.swap(other.pimpl_);
}

Das Pimpl-Idiom hat den Nachteil einer zusätzlichen dynamischen Speicheranforderung, bietet aber gleichzeitig den Vorteil, dass Implementierungsdetails versteckt werden. In diesem Beispiel bedeutet das, dass die Definition von A und std::string in der Klassendefinition von bogus nicht bekannt sein muss und die jeweiligen Header nur in der Implemtierungsdatei bogus.cpp inkludiert werden müssen. Das hat zur Folge, dass Änderungen an A die Definition von bogus nicht betreffen und die Kunden von bogus nicht neu kompilieren müssen.

Optimierung

Fast alle modernen Compiler nehmen eine Optimierung vor, die vom C++ Standard nicht vorgeschrieben aber erlaubt ist. Man spricht von Copy Elision, wenn unnötige Kopien von temporären Objekten durch den Compiler wegoptimiert werden. Dies trifft z.B. zu, wenn ein temporäres Objekt (ein sog. rvalue) als Argument by value einer Funktion übergeben wird. D.h. dass eine Funktion, die ein Funktionsargument kopiert, dies nicht als explizite Kopie einer übergebenen Referenz, sondern als Übernahme by value durchführen sollte, um eine Optimierung zu ermöglichen. Für das Überladen des Zuweisungsoperators von bogus bedeutet das:

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

Für die folgende Anweisung hat diese Änderung keinerlei Auswirkungen:

bogus a;
bogus b;
b = a;

Wird dem Zuweisungsoperator jedoch ein temporäres Objekt übergeben, kann eine Kopie vom Compiler ausgelassen werden.

bogus make_bogus();
bogus b;
b = make_bogus();

Diskussion

Sende ein Kommentar, Frage, Korrekturen, Beschimpfungen...

Name:

Nachricht:


doxapp c++
Zur Übersicht
home