it-swarm.com.de

Was ist die Copy-and-Swap-Sprache?

Was ist diese Redewendung und wann sollte sie verwendet werden? Welche Probleme löst es? Ändert sich das Idiom, wenn C++ 11 verwendet wird?

Obwohl es an vielen Stellen erwähnt wurde, hatten wir keine einzige "Was ist das?" - Frage und Antwort, also hier ist es. Hier ist eine unvollständige Liste der Orte, an denen es zuvor erwähnt wurde:

1873
GManNickG

Überblick

Warum brauchen wir die Copy-and-Swap-Sprache?

Jede Klasse, die eine Ressource verwaltet (ein Wrapper, wie ein Smart Pointer), muss The Big Three implementieren. Während die Ziele und die Implementierung des Kopierkonstruktors und des Destruktors einfach sind, ist der Kopierzuweisungsoperator wohl der nuancierteste und schwierigste. Wie soll es gemacht werden? Welche Fallstricke müssen vermieden werden?

Das Copy-and-Swap-Idiom ist die Lösung und unterstützt den Zuweisungsoperator auf elegante Weise dabei, zwei Dinge zu erreichen: Vermeiden von Codeduplizierungen und Bereitstellen eines starke Ausnahmegarantie .

Wie funktioniert es?

Konzeptionell wird die Funktion des Kopierkonstruktors verwendet, um eine lokale Kopie der Daten zu erstellen. Anschließend werden die kopierten Daten mit der Funktion swap ausgetauscht und die alten Daten ausgetauscht Daten mit den neuen Daten. Die temporäre Kopie zerstört dann und nimmt die alten Daten mit. Wir haben eine Kopie der neuen Daten.

Um die Copy-and-Swap-Sprache zu verwenden, benötigen wir drei Dinge: einen funktionierenden Copy-Konstruktor, einen funktionierenden Destruktor (beide sind die Basis eines jeden Wrappers, sollten also trotzdem vollständig sein) und ein swap Funktion.

Eine Swap-Funktion ist eine Funktion nicht werfen, die zwei Objekte einer Klasse, Mitglied für Mitglied, vertauscht. Wir könnten versucht sein, std::swap Zu verwenden, anstatt unser eigenes bereitzustellen, aber das wäre unmöglich; std::swap Verwendet den Kopierkonstruktor und den Kopierzuweisungsoperator in seiner Implementierung, und wir würden letztendlich versuchen, den Zuweisungsoperator in sich selbst zu definieren!

(Nicht nur das, sondern auch unqualifizierte Aufrufe von swap verwenden unseren benutzerdefinierten Swap-Operator und überspringen die unnötige Konstruktion und Zerstörung unserer Klasse, die std::swap Mit sich bringen würde.)


Eine ausführliche Erklärung

Das Ziel

Betrachten wir einen konkreten Fall. Wir möchten in einer ansonsten nutzlosen Klasse ein dynamisches Array verwalten. Wir beginnen mit einem funktionierenden Konstruktor, Kopierkonstruktor und Destruktor:

#include <algorithm> // std::copy
#include <cstddef> // std::size_t

class dumb_array
{
public:
    // (default) constructor
    dumb_array(std::size_t size = 0)
        : mSize(size),
          mArray(mSize ? new int[mSize]() : nullptr)
    {
    }

    // copy-constructor
    dumb_array(const dumb_array& other)
        : mSize(other.mSize),
          mArray(mSize ? new int[mSize] : nullptr),
    {
        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy(other.mArray, other.mArray + mSize, mArray);
    }

    // destructor
    ~dumb_array()
    {
        delete [] mArray;
    }

private:
    std::size_t mSize;
    int* mArray;
};

Diese Klasse verwaltet das Array fast erfolgreich, benötigt jedoch operator=, Um korrekt zu funktionieren.

Eine gescheiterte Lösung

So könnte eine naive Implementierung aussehen:

// the hard part
dumb_array& operator=(const dumb_array& other)
{
    if (this != &other) // (1)
    {
        // get rid of the old data...
        delete [] mArray; // (2)
        mArray = nullptr; // (2) *(see footnote for rationale)

        // ...and put in the new
        mSize = other.mSize; // (3)
        mArray = mSize ? new int[mSize] : nullptr; // (3)
        std::copy(other.mArray, other.mArray + mSize, mArray); // (3)
    }

    return *this;
}

Und wir sagen, wir sind fertig; Dies verwaltet jetzt ein Array ohne Lecks. Es treten jedoch drei Probleme auf, die im Code nacheinander als (n) Gekennzeichnet sind.

  1. Der erste ist der Selbstzuweisungstest. Diese Überprüfung dient zwei Zwecken: Sie verhindert auf einfache Weise, dass unnötiger Code bei der Selbstzuweisung ausgeführt wird, und schützt uns vor subtilen Fehlern (z. B. Löschen des Arrays, um es zu kopieren). In allen anderen Fällen dient es lediglich dazu, das Programm zu verlangsamen und im Code als Rauschen zu wirken. Selbstzuweisung tritt selten auf, daher ist diese Überprüfung in den meisten Fällen eine Verschwendung. Es wäre besser, wenn der Bediener ohne sie richtig arbeiten könnte.

  2. Das zweite ist, dass es nur eine grundlegende Ausnahmegarantie bietet. Wenn new int[mSize] Fehlschlägt, wurde *this Geändert. (Die Größe ist nämlich falsch und die Daten sind weg!) Für eine starke Ausnahmegarantie müsste es sich um Folgendes handeln:

    dumb_array& operator=(const dumb_array& other)
    {
        if (this != &other) // (1)
        {
            // get the new data ready before we replace the old
            std::size_t newSize = other.mSize;
            int* newArray = newSize ? new int[newSize]() : nullptr; // (3)
            std::copy(other.mArray, other.mArray + newSize, newArray); // (3)
    
            // replace the old data (all are non-throwing)
            delete [] mArray;
            mSize = newSize;
            mArray = newArray;
        }
    
        return *this;
    }
    
  3. Der Code wurde erweitert! Was uns zum dritten Problem führt: Code-Duplikation. Unser Zuweisungsoperator dupliziert effektiv den gesamten Code, den wir bereits an anderer Stelle geschrieben haben, und das ist eine schreckliche Sache.

In unserem Fall besteht der Kern nur aus zwei Zeilen (die Zuordnung und die Kopie), aber bei komplexeren Ressourcen kann dieses Aufblähen des Codes ein ziemlicher Aufwand sein. Wir sollten uns bemühen, uns nie zu wiederholen.

(Man könnte sich fragen: Wenn so viel Code benötigt wird, um eine Ressource richtig zu verwalten, was, wenn meine Klasse mehr als eine verwaltet? Dies scheint zwar ein berechtigtes Problem zu sein, erfordert aber in der Tat nicht-triviale try/catch -Klauseln, dies ist kein Problem, da eine Klasse verwalten sollte nur eine Ressource !)

Eine erfolgreiche Lösung

Wie bereits erwähnt, behebt das Copy-and-Swap-Verfahren alle diese Probleme. Aber im Moment haben wir alle Anforderungen mit einer Ausnahme: eine swap -Funktion. Während die Dreierregel erfolgreich die Existenz unseres Kopierkonstruktors, Zuweisungsoperators und Destruktors voraussetzt, sollte sie eigentlich "Die großen Dreieinhalb" heißen: Jedes Mal, wenn Ihre Klasse eine Ressource verwaltet, ist es auch sinnvoll, eine swap Funktion.

Wir müssen unserer Klasse Swap-Funktionen hinzufügen, und das tun wir wie folgt †:

class dumb_array
{
public:
    // ...

    friend void swap(dumb_array& first, dumb_array& second) // nothrow
    {
        // enable ADL (not necessary in our case, but good practice)
        using std::swap;

        // by swapping the members of two objects,
        // the two objects are effectively swapped
        swap(first.mSize, second.mSize);
        swap(first.mArray, second.mArray);
    }

    // ...
};

( Hier ist die Erklärung, warum public friend swap.) Jetzt können wir nicht nur unsere dumb_array Austauschen, sondern auch im Allgemeinen effizienter tauschen ; Es werden lediglich Zeiger und Größen ausgetauscht, anstatt ganze Arrays zuzuweisen und zu kopieren. Abgesehen von diesem Plus an Funktionalität und Effizienz können wir jetzt die Copy-and-Swap-Sprache implementieren.

Unser Zuweisungsoperator ist ohne weiteres:

dumb_array& operator=(dumb_array other) // (1)
{
    swap(*this, other); // (2)

    return *this;
}

Und das ist es! Mit einem Schlag werden alle drei Probleme elegant auf einmal gelöst.

Warum funktioniert es?

Wir bemerken zuerst eine wichtige Wahl: das Parameterargument wird genommen nach Wert. Während man genauso gut folgendes tun könnte (und tatsächlich tun es viele naive Implementierungen des Idioms):

dumb_array& operator=(const dumb_array& other)
{
    dumb_array temp(other);
    swap(*this, temp);

    return *this;
}

Wir verlieren eine wichtige Optimierungsmöglichkeit . Nicht nur das, sondern auch diese Auswahl ist in C++ 11 von entscheidender Bedeutung, auf das später eingegangen wird. (Im Allgemeinen ist eine bemerkenswert nützliche Richtlinie wie folgt: Wenn Sie eine Kopie von etwas in einer Funktion erstellen möchten, lassen Sie den Compiler dies in der Parameterliste tun. ‡)

In beiden Fällen ist diese Methode zum Abrufen unserer Ressource der Schlüssel zur Beseitigung von Codeduplizierungen: Wir können den Code des Kopierkonstruktors verwenden, um die Kopie zu erstellen, und müssen nie etwas davon wiederholen. Nachdem die Kopie erstellt wurde, können wir sie austauschen.

Beachten Sie, dass beim Aufrufen der Funktion alle neuen Daten bereits zugeordnet, kopiert und einsatzbereit sind. Dies gibt uns eine starke kostenlose Ausnahmegarantie: Wir werden die Funktion nicht einmal aufrufen, wenn die Erstellung der Kopie fehlschlägt, und es ist daher nicht möglich, den Status von *this Zu ändern. (Was wir zuvor manuell für eine starke Ausnahmegarantie getan haben, tut der Compiler jetzt für uns; wie nett.)

Zu diesem Zeitpunkt sind wir frei von Zuhause, da swap nicht wirft. Wir tauschen unsere aktuellen Daten mit den kopierten Daten aus, um unseren Status sicher zu ändern, und die alten Daten werden in das temporäre Verzeichnis gestellt. Die alten Daten werden dann freigegeben, wenn die Funktion zurückkehrt. (Wobei der Gültigkeitsbereich des Parameters endet und sein Destruktor aufgerufen wird.)

Da das Idiom keinen Code wiederholt, können wir keine Fehler im Operator einführen. Beachten Sie, dass wir dadurch keine Selbstzuweisungsprüfung mehr benötigen und eine einheitliche Implementierung von operator= Ermöglichen. (Außerdem haben wir keine Leistungsstrafe mehr für Nicht-Selbstzuweisungen.)

Und das ist die Copy-and-Swap-Sprache.

Was ist mit C++ 11?

Die nächste Version von C++, C++ 11, nimmt eine sehr wichtige Änderung in Bezug auf die Verwaltung von Ressourcen vor: Die Dreierregel ist jetzt Die Vierergruppe (und einhalb). Warum? Da wir nicht nur in der Lage sein müssen, unsere Ressource zu kopieren und zu konstruieren, müssen wir sie auch verschieben und konstruieren .

Zum Glück ist das ganz einfach:

class dumb_array
{
public:
    // ...

    // move constructor
    dumb_array(dumb_array&& other)
        : dumb_array() // initialize via default constructor, C++11 only
    {
        swap(*this, other);
    }

    // ...
};

Was ist hier los? Erinnern Sie sich an das Ziel der Bewegungskonstruktion: die Ressourcen einer anderen Instanz der Klasse zu entnehmen und sie in einem Zustand zu belassen, der garantiert zuweisbar und zerstörbar ist.

Das, was wir getan haben, ist einfach: Initialisieren Sie es über den Standardkonstruktor (eine C++ 11-Funktion) und tauschen Sie es mit other aus. Wir wissen, dass eine standardmäßig erstellte Instanz unserer Klasse sicher zugewiesen und zerstört werden kann, sodass wir wissen, dass other nach dem Tauschen dasselbe tun kann.

(Beachten Sie, dass einige Compiler die Konstruktordelegierung nicht unterstützen. In diesem Fall müssen wir die Klasse manuell standardmäßig erstellen. Dies ist eine unglückliche, aber glücklicherweise triviale Aufgabe.)

Warum funktioniert das?

Das ist die einzige Änderung, die wir an unserer Klasse vornehmen müssen. Warum funktioniert das? Denken Sie an die wichtige Entscheidung, den Parameter als Wert und nicht als Referenz festzulegen:

dumb_array& operator=(dumb_array other); // (1)

Wenn nun other mit einem r-Wert initialisiert wird, wird es wird bewegungskonstruiert. Perfekt. Auf die gleiche Weise wie in C++ 03 können wir unsere Kopierkonstruktorfunktionalität wiederverwenden, indem wir das Argument by-value verwenden. In C++ 11 wird automatisch gegebenenfalls auch der Verschiebungskonstruktor ausgewählt . (Und natürlich kann, wie in dem zuvor verlinkten Artikel erwähnt, das Kopieren/Verschieben des Wertes einfach ganz entfallen.)

Und so schließt die Copy-and-Swap-Sprache.


Fußnoten

* Warum setzen wir mArray auf null? Wenn ein weiterer Code im Operator ausgelöst wird, kann der Destruktor von dumb_array Aufgerufen werden. und wenn dies geschieht, ohne es auf null zu setzen, versuchen wir, Speicher zu löschen, der bereits gelöscht wurde! Wir vermeiden dies, indem wir es auf null setzen, da das Löschen von null keine Operation ist.

† Es gibt andere Behauptungen, wir sollten uns auf std::swap Für unseren Typ spezialisieren, eine klasseninterne swap neben einer freien Funktion swap usw. bereitstellen. Aber das ist alles unnötig: jede ordnungsgemäße Verwendung von swap erfolgt durch einen unqualifizierten Aufruf, und unsere Funktion wird durch [~ # ~] adl [~ # ~] ermittelt. Eine Funktion reicht aus.

‡ Der Grund ist einfach: Sobald Sie die Ressource für sich haben, können Sie sie austauschen und/oder verschieben (C++ 11), wo immer sie sein muss. Und indem Sie die Kopie in der Parameterliste erstellen, maximieren Sie die Optimierung.

2050
GManNickG

Die Zuweisung erfolgt in zwei Schritten: den alten Zustand des Objekts abreißen und das neue erstellen Zustand als Kopie des Zustands eines anderen Objekts.

Grundsätzlich ist es das, was der Destruktor und der Kopierkonstruktor tun, Die erste Idee wäre also, die Arbeit an sie zu delegieren. Da jedoch die Zerstörung nicht scheitern darf, obwohl die Konstruktion dies könnte, wollen wir eigentlich umgekehrt machen: führen Sie zuerst den konstruktiven Teil durch und wenn dies erfolgreich war, dann mache den destruktiven Teil. Das Copy-and-Swap-Idiom ist eine Möglichkeit, genau das zu tun: Es ruft zuerst den Copy-Konstruktor einer Klasse auf, um ein temporäres Objekt zu erstellen, tauscht dann seine Daten mit den temporären Objekten aus und lässt dann den Destruktor des temporären Objekts den alten Zustand zerstören.
Da swap() niemals scheitern soll, ist der einzige Teil, der scheitern könnte, die Kopierkonstruktion. Dies wird zuerst ausgeführt, und wenn dies fehlschlägt, wird im Zielobjekt nichts geändert.

In seiner verfeinerten Form wird Copy-and-Swap implementiert, indem das Kopieren durch Initialisieren des Parameters (non-reference) des Zuweisungsoperators ausgeführt wird:

T& operator=(T tmp)
{
    this->swap(tmp);
    return *this;
}
257
sbi

Es gibt bereits einige gute Antworten. Ich werde mich hauptsächlich auf das konzentrieren, was meiner Meinung nach fehlt - eine Erklärung der "Nachteile" mit der Copy-and-Swap-Sprache ....

Was ist die Copy-and-Swap-Sprache?

Eine Möglichkeit, den Zuweisungsoperator im Sinne einer Swap-Funktion zu implementieren:

X& operator=(X rhs)
{
    swap(rhs);
    return *this;
}

Die Grundidee ist, dass:

  • der fehleranfälligste Teil der Zuweisung zu einem Objekt besteht darin, sicherzustellen, dass alle Ressourcen erfasst werden, die der neue Status benötigt (z. B. Speicher, Deskriptoren).

  • diese Erfassung kann versucht werden vor den aktuellen Status des Objekts zu ändern (dh *this), wenn eine Kopie des neuen Werts erstellt wird, weshalb rhs akzeptiert wird nach Wert (dh kopiert) statt nach Referenz

  • das Austauschen des Status der lokalen Kopie rhs und *this ist in der Regel relativ einfach, da die lokale Kopie anschließend keinen bestimmten Status benötigt (Benötigt nur eine Zustandsanpassung, damit der Destruktor ausgeführt werden kann, ähnlich wie für ein Objekt, das verschoben von in> = C++ 11 ist.)

Wann sollte es angewendet werden? (Welche Probleme löst es [/ create]?)

  • Wenn Sie möchten, dass das zugewiesene Objekt von einer Zuweisung, die eine Ausnahme auslöst, unberührt bleibt, vorausgesetzt, Sie haben ein swap mit starker Ausnahmegarantie, und im Idealfall eines, das nicht fehlschlagen kann/throw. †

  • Wenn Sie einen übersichtlichen, leicht verständlichen und robusten Weg suchen, den Zuweisungsoperator in Bezug auf (einfacheren) Kopierkonstruktor, swap und Destruktorfunktionen zu definieren.

    • Die Selbstzuweisung durch Kopieren und Vertauschen vermeidet häufig übersehene Edge-Fälle. ‡
  • Wenn Leistungseinbußen oder eine vorübergehend höhere Ressourcennutzung, die durch ein zusätzliches temporäres Objekt während der Zuweisung verursacht wird, für Ihre Anwendung nicht wichtig sind. ⁂

swap Throwing: Es ist im Allgemeinen möglich, Datenelemente, die von den Objekten anhand von Zeigern verfolgt werden, zuverlässig auszutauschen, Datenelemente, die keinen Throw-Free-Swap haben oder für die das Austauschen als X tmp = lhs; lhs = rhs; rhs = tmp; implementiert werden muss. und die Erstellung oder Zuweisung von Kopien kann möglicherweise fehlschlagen, wenn einige Datenelemente ausgetauscht werden und andere nicht. Dieses Potenzial gilt auch für C++ 03 std::strings, wenn James eine andere Antwort kommentiert:

@wilhelmtell: In C++ 03 werden keine Ausnahmen erwähnt, die möglicherweise von std :: string :: swap (das von std :: swap aufgerufen wird) ausgelöst werden. In C++ 0x ist std :: string :: swap noexcept und darf keine Ausnahmen auslösen. - James McNellis 22. Dezember 10 um 15:24


‡ Die Implementierung eines Zuweisungsoperators, die bei der Zuweisung von einem bestimmten Objekt sinnvoll erscheint, kann bei der Selbstzuweisung leicht fehlschlagen. Während es unvorstellbar erscheint, dass Client-Code sogar versucht, sich selbst zuzuweisen, kann es bei Algo-Operationen auf Containern mit x = f(x); Code relativ leicht passieren, wobei f (möglicherweise nur für einige #ifdef-Zweige) ein Makro ist ala #define f(x) x oder eine Funktion, die einen Verweis auf x zurückgibt, oder sogar (wahrscheinlich ineffizienter, aber präziser) Code wie x = c1 ? x * 2 : c2 ? x / 2 : x;). Zum Beispiel:

struct X
{
    T* p_;
    size_t size_;
    X& operator=(const X& rhs)
    {
        delete[] p_;  // OUCH!
        p_ = new T[size_ = rhs.size_];
        std::copy(p_, rhs.p_, rhs.p_ + rhs.size_);
    }
    ...
};

Bei der Selbstzuweisung zeigt der x.p_; des obigen Codes delete p_ auf eine neu zugewiesene Heap-Region und versucht dann, die darin enthaltenen uninitialised -Daten zu lesen (undefiniertes Verhalten), falls dies nicht der Fall ist Tu etwas zu Seltsames, copy versuche eine Selbstzuweisung zu jedem gerade zerstörten 'T'!


⁂ Das Copy-and-Swap-Idiom kann Ineffizienzen oder Einschränkungen aufgrund der Verwendung eines zusätzlichen temporären Parameters verursachen (wenn der Parameter des Operators durch Kopieren erstellt wird):

struct Client
{
    IP_Address ip_address_;
    int socket_;
    X(const X& rhs)
      : ip_address_(rhs.ip_address_), socket_(connect(rhs.ip_address_))
    { }
};

Hier könnte ein handgeschriebener Client::operator= prüfen, ob *this bereits mit demselben Server wie rhs verbunden ist (möglicherweise wird ein "Reset" -Code gesendet, wenn dies sinnvoll ist), während der Copy-and-Swap-Ansatz den Copy-Konstruktor aufruft Das würde wahrscheinlich geschrieben werden, um eine bestimmte Socket-Verbindung zu öffnen und dann die ursprüngliche zu schließen. Dies könnte nicht nur eine Remote-Netzwerkinteraktion anstelle einer einfachen In-Process-Variablenkopie bedeuten, sondern auch die Client- oder Server-Beschränkungen für Socket-Ressourcen oder -Verbindungen verletzen. (Natürlich hat diese Klasse ein ziemlich schreckliches Interface, aber das ist eine andere Sache ;-P).

38
Tony Delroy

Diese Antwort ist eher eine Ergänzung und eine geringfügige Änderung der obigen Antworten.

In einigen Versionen von Visual Studio (und möglicherweise auch in anderen Compilern) gibt es einen Fehler, der wirklich nervt und keinen Sinn ergibt. Wenn Sie also Ihre swap -Funktion wie folgt deklarieren/definieren:

friend void swap(A& first, A& second) {

    std::swap(first.size, second.size);
    std::swap(first.arr, second.arr);

}

... der Compiler wird Sie anschreien, wenn Sie die Funktion swap aufrufen:

enter image description here

Dies hat etwas damit zu tun, dass eine friend -Funktion aufgerufen und ein this -Objekt als Parameter übergeben wird.


Eine Möglichkeit, dies zu umgehen, besteht darin, das Schlüsselwort friend nicht zu verwenden und die Funktion swap neu zu definieren:

void swap(A& other) {

    std::swap(size, other.size);
    std::swap(arr, other.arr);

}

Dieses Mal können Sie einfach swap aufrufen und other übergeben, was den Compiler glücklich macht:

enter image description here


Schließlich müssen Sie nicht eine friend -Funktion verwenden, um 2 Objekte zu tauschen. Ebenso sinnvoll ist es, swap zu einer Member-Funktion zu machen, die ein other -Objekt als Parameter hat.

Sie haben bereits Zugriff auf das Objekt this, daher ist die Übergabe als Parameter technisch redundant.

22
Oleksiy

Ich möchte ein Wort der Warnung hinzufügen, wenn Sie sich mit C++ 11-ähnlichen allokator-fähigen Containern beschäftigen. Tauschen und Zuweisen haben eine subtil unterschiedliche Semantik.

Betrachten wir der Vollständigkeit halber einen Container std::vector<T, A>, wobei A ein zustandsbehafteter Allokator-Typ ist, und vergleichen wir die folgenden Funktionen:

void fs(std::vector<T, A> & a, std::vector<T, A> & b)
{ 
    a.swap(b);
    b.clear(); // not important what you do with b
}

void fm(std::vector<T, A> & a, std::vector<T, A> & b)
{
    a = std::move(b);
}

Der Zweck beider Funktionen fs und fm besteht darin, a den Zustand zu geben, den b ursprünglich hatte. Es gibt jedoch eine versteckte Frage: Was passiert, wenn a.get_allocator() != b.get_allocator()? Die Antwort lautet: Es kommt darauf an. Schreiben wir AT = std::allocator_traits<A>.

  • Wenn AT::propagate_on_container_move_assignmentstd::true_type ist, ordnet fm den Zuweiser von a dem Wert von b.get_allocator() zu, andernfalls nicht, und a fährt fort um den ursprünglichen Allokator zu verwenden. In diesem Fall müssen die Datenelemente einzeln ausgetauscht werden, da die Speicherung von a und b nicht kompatibel ist.

  • Wenn AT::propagate_on_container_swapstd::true_type ist, dann tauscht fs sowohl Daten als auch Allokatoren in der erwarteten Weise aus.

  • Wenn AT::propagate_on_container_swapstd::false_type ist, benötigen wir eine dynamische Prüfung.

    • Wenn a.get_allocator() == b.get_allocator(), verwenden die beiden Container kompatiblen Speicher, und der Austausch erfolgt auf die übliche Weise.
    • Wenn jedoch a.get_allocator() != b.get_allocator(), hat das Programm ndefiniertes Verhalten (vgl. [Container.requirements.general/8].

Das Ergebnis ist, dass das Austauschen in C++ 11 zu einer nicht trivialen Operation geworden ist, sobald Ihr Container Stateful Allocators unterstützt. Das ist ein etwas "fortgeschrittener Anwendungsfall", aber nicht ganz unwahrscheinlich, da Bewegungsoptimierungen normalerweise erst dann interessant werden, wenn Ihre Klasse eine Ressource verwaltet und der Speicher eine der beliebtesten Ressourcen ist.

13
Kerrek SB