Frage Was ist das Kopier-und-Tausch-Idiom?


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

Obwohl es an vielen Orten erwähnt wurde, hatten wir keine singuläre "Was ist das" Frage und Antwort, also ist es hier. Hier ist eine unvollständige Liste von Orten, wo es zuvor erwähnt wurde:


1668
2017-07-19 08:42


Ursprung


Antworten:


Überblick

Warum brauchen wir das Copy-and-Swap-Idiom?

Jede Klasse, die eine Ressource verwaltet (a Verpackung, wie ein intelligenter Zeiger) muss implementieren Die Großen Drei. Während die Ziele und die Implementierung des Kopierkonstruktors und Destruktors einfach sind, ist der Kopierzuweisungsoperator wohl der nuancierteste und schwierigste. Wie sollte es gemacht werden? Welche Fallgruben müssen vermieden werden?

Das Kopieren-und-Tauschen-Idiom ist die Lösung und unterstützt den Zuweisungsoperator elegant dabei, zwei Dinge zu erreichen: Vermeiden Code-Duplizierungund Bereitstellung von a starke Ausnahmegarantie.

Wie funktioniert es?

KonzeptionellEs verwendet die Funktionen des Kopierkonstruktors, um eine lokale Kopie der Daten zu erstellen. Anschließend werden die kopierten Daten mit a übernommen swap Funktion, tauschen Sie die alten Daten mit den neuen Daten aus. Die temporäre Kopie zerstört dann und nimmt die alten Daten mit. Uns bleibt eine Kopie der neuen Daten.

Um das Copy-and-Swap-Idiom zu verwenden, benötigen wir drei Dinge: einen funktionierenden Copy-Constructor, einen funktionierenden Destruktor (beide sind die Basis jedes Wrappers, sollten also trotzdem vollständig sein) und a swap Funktion.

Eine Tauschfunktion ist a nicht werfen Funktion, die zwei Objekte einer Klasse, Mitglied für Mitglied, vertauscht. Wir könnten versucht sein zu verwenden std::swap anstatt unser eigenes zu liefern, aber das wäre unmöglich; std::swap verwendet den Copy-Constructor- und Copy-Assignment-Operator innerhalb seiner Implementierung und wir würden letztendlich versuchen, den Zuweisungsoperator in Bezug auf sich selbst zu definieren!

(Nicht nur das, aber unqualifizierte Aufrufe an swap Wir werden unseren benutzerdefinierten Tauschoperator verwenden und dabei die unnötige Konstruktion und Zerstörung unserer Klasse überspringen std::swap würde dazu führen.)


Eine eingehende Erklärung

Das Ziel

Betrachten wir einen konkreten Fall. Wir wollen in einer ansonsten nutzlosen Klasse ein dynamisches Array verwalten. Wir beginnen mit einem funktionierenden Konstruktor, einem Kopierkonstruktor und einem 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 es aber operator= richtig funktionieren.

Eine fehlgeschlagene 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 leidet jedoch unter drei Problemen, die im Code als sequentiell gekennzeichnet sind (n).

  1. Der erste ist der Selbstzuweisungstest. Diese Überprüfung dient zwei Zwecken: Es ist ein einfacher Weg, uns davon abzuhalten, unnötigen Code bei der Selbstzuweisung auszuführen, und er schützt uns vor subtilen Fehlern (wie das Löschen des Arrays, nur um es zu kopieren und zu kopieren). Aber in allen anderen Fällen dient es lediglich dazu, das Programm zu verlangsamen und als Rauschen im Code zu wirken; Eine Selbstzuweisung tritt selten auf, daher ist diese Überprüfung meistens eine Verschwendung. Es wäre besser, wenn der Bediener ohne ihn richtig arbeiten könnte.

  2. Das zweite ist, dass es nur eine grundlegende Ausnahmegarantie bietet. Ob new int[mSize] scheitert, *this wird geändert haben. (Nämlich die Größe ist falsch und die Daten sind weg!) Für eine starke Ausnahmegarantie müsste es etwas sein wie:

    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: Codeduplizierung. Unser Zuweisungs-Operator dupliziert effektiv den ganzen Code, den wir bereits anderswo geschrieben haben, und das ist eine schreckliche Sache.

In unserem Fall besteht der Kern aus nur zwei Zeilen (der Zuweisung und der Kopie), aber mit komplexeren Ressourcen kann dieser Code-Aufwand ziemlich mühsam sein. Wir sollten danach streben, uns niemals zu wiederholen.

(Man könnte sich fragen: Wenn so viel Code benötigt wird, um eine Ressource korrekt zu verwalten, was passiert, wenn meine Klasse mehr als einen verwaltet?) Dies scheint zwar eine berechtigte Sorge zu sein und erfordert in der Tat nicht-triviales try/catch Klauseln, dies ist ein Nicht-Problem. Das ist, weil eine Klasse verwalten sollte nur eine Ressource!)

Eine erfolgreiche Lösung

Wie erwähnt, wird das Kopier-und-Austausch-Idiom alle diese Probleme beheben. Aber jetzt haben wir alle Anforderungen außer einem: a swap Funktion. Während die Regel von Drei erfolgreich die Existenz unseres Kopierkonstruktors, Zuweisungsoperators und Destruktors mit sich bringt, sollte es wirklich "Die Großen Drei und Eine Hälfte" heißen: Jedes Mal, wenn Ihre Klasse eine Ressource verwaltet, ist es auch sinnvoll, eine swap Funktion.

Wir müssen unserer Klasse Swap-Funktionalität hinzufügen, und wir tun das 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 tauschen dumb_array, aber Swaps im Allgemeinen können effizienter sein; Es tauscht einfach Zeiger und Größen aus, anstatt ganze Arrays zuzuordnen und zu kopieren. Abgesehen von diesem Bonus in Funktionalität und Effizienz sind wir nun bereit, das Kopier- und Austausch-Idiom zu implementieren.

Unser Zuweisungs-Operator 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 gleichzeitig elegant angegangen.

Warum funktioniert es?

Wir bemerken zuerst eine wichtige Entscheidung: Das Parameterargument wird genommen Nebenwert. Man könnte genauso gut Folgendes tun (und tatsächlich tun viele naive Implementierungen des Idioms):

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

    return *this;
}

Wir verlieren ein wichtige Optimierungsmöglichkeit. Nicht nur das, sondern diese Wahl ist in C ++ 11 kritisch, was später diskutiert wird. (Im Allgemeinen ist eine bemerkenswert nützliche Richtlinie wie folgt: Wenn Sie eine Kopie von etwas in einer Funktion erstellen wollen, lassen Sie es vom Compiler in der Parameterliste tun.)

In jedem Fall ist diese Methode, um unsere Ressource zu erhalten, der Schlüssel zur Beseitigung von Code-Duplizierung: Wir können den Code aus dem Copy-Konstruktor verwenden, um die Kopie zu erstellen, und müssen nie etwas davon wiederholen. Jetzt, wo die Kopie gemacht ist, sind wir bereit zu tauschen.

Beachten Sie, dass beim Eingeben der Funktion alle neuen Daten bereits zugewiesen, kopiert und zur Verwendung bereit sind. Dies gibt uns eine starke Ausnahme-Garantie für die kostenlose: wir werden nicht einmal die Funktion eingeben, wenn der Aufbau der Kopie fehlschlägt, und es ist daher nicht möglich, den Zustand zu ändern *this. (Was wir vorher manuell für eine starke Ausnahmegarantie gemacht haben, tut der Compiler jetzt für uns; wie nett.)

An diesem Punkt sind wir frei, weil swap ist nicht werfen. Wir tauschen unsere aktuellen Daten mit den kopierten Daten aus und ändern so sicher unseren Zustand, und die alten Daten werden in das temporäre übertragen. Die alten Daten werden dann freigegeben, wenn die Funktion zurückkehrt. (Wo der Gültigkeitsbereich des Parameters endet und sein Destruktor aufgerufen wird.)

Da das Idiom keinen Code wiederholt, können wir keine Fehler innerhalb des Operators einführen. Beachten Sie, dass dies bedeutet, dass wir die Notwendigkeit einer Überprüfung der Selbstzuweisung weglassen, die eine einheitliche Implementierung von operator=. (Außerdem haben wir keine Leistungseinbußen bei nicht-selbst zugewiesenen Aufgaben.)

Und das ist das Kopieren-und-Tauschen-Idiom.

Was ist mit C ++ 11?

Die nächste Version von C ++, C ++ 11, macht eine sehr wichtige Änderung in der Art und Weise, wie wir Ressourcen verwalten: Die Drei-Regel ist jetzt Die Viererregel (und einhalb). Warum? Weil wir nicht nur in der Lage sein müssen, unsere Ressource zu kopieren, wir müssen es auch konstruieren.

Zum Glück für uns ist das 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 denn hier los? Erinnern Sie sich an das Ziel von move-construction: die Ressourcen einer anderen Instanz der Klasse zu übernehmen und sie in einem Zustand zu belassen, der garantiert zuweisbar und zerstörbar ist.

Was wir getan haben, ist einfach: Initialisiere über den Standardkonstruktor (eine C ++ 11-Funktion) und tausche dann mit other; Wir wissen, dass eine standardkonstruierte Instanz unserer Klasse sicher zugewiesen und zerstört werden kann other kann nach dem Austausch dasselbe tun.

(Beachten Sie, dass einige Compiler die Delegierung von Konstruktoren nicht unterstützen; in diesem Fall müssen wir die Klasse standardmäßig konstruieren. Dies ist eine unglückliche, aber glücklicherweise triviale Aufgabe.)

Warum funktioniert das?

Das ist die einzige Veränderung, die wir in unserer Klasse machen müssen. Warum funktioniert das? Erinnere dich an die immer wichtige Entscheidung, die wir getroffen haben, um den Parameter zu einem Wert und nicht zu einer Referenz zu machen:

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

Nun, wenn other wird mit einem rvalue initialisiert, es wird move-constructed sein. Perfekt. Genauso können wir in C ++ 03 unsere Kopierkonstruktorfunktion wiederverwenden, indem wir das Argument by-value nehmen, C ++ 11 wird automatisch Wählen Sie auch den Move-Konstruktor aus, wenn es angebracht ist. (Und natürlich, wie im vorher verlinkten Artikel erwähnt, kann das Kopieren / Verschieben des Wertes einfach ganz weggelassen werden.)

Und so schließt das Kopier-und-Tausch-Idiom.


Fußnoten

* Warum stellen wir fest? mArray zu null? Weil, wenn weiterer Code im Operator wirft, der Destruktor von dumb_array könnte genannt werden; und wenn dies geschieht, ohne es auf null zu setzen, versuchen wir, bereits gelöschten Speicher zu löschen! Wir vermeiden dies, indem wir ihn auf null setzen, da das Löschen von null eine Nicht-Operation ist.

† Es gibt andere Behauptungen, auf die wir uns spezialisieren sollten std::swap für unseren Typ, bieten Sie eine In-Klasse swap neben einer Frei-Funktion swapusw. Aber das ist alles unnötig: eine ordnungsgemäße Verwendung von swap wird durch einen unqualifizierten Anruf, und unsere Funktion wird durch gefunden werden ADL. Eine Funktion wird ausreichen.

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


1835
2017-07-19 08:43



Die Aufgabe besteht im Kern aus zwei Schritten: den alten Zustand des Objekts abreißen und seinen neuen Zustand als Kopie erstellen des Zustands eines anderen Objekts.

Im Grunde ist das das, was Destruktor und das Konstruktor kopieren tun, also wäre die erste Idee, die Arbeit an sie zu delegieren. Da jedoch die Zerstörung nicht scheitern darf, während der Bau könnte, wir wollen es eigentlich anders herum machen: zuerst den konstruktiven Teil durchführen und wenn das gelang, dann mach den zerstörerischen Teil. Das Copy-and-Swap-Idiom ist eine Möglichkeit, genau das zu tun: Zuerst ruft es einen Kopierkonstruktor der Klasse auf, um ein temporäres zu erstellen, tauscht dann seine Daten mit den temporären aus und lässt dann den Destruktor des temporären Destruktors den alten Zustand zerstören.
Schon seit swap() soll nie versagen, der einzige Teil, der scheitern könnte, ist die Kopierkonstruktion. Dies wird zuerst ausgeführt, und wenn es fehlschlägt, wird im Zielobjekt nichts geändert.

In seiner verfeinerten Form wird copy-and-swap implementiert, indem die Kopie durch Initialisierung des (nicht referenzierten) Parameters des Zuweisungsoperators durchgeführt wird:

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

226
2017-07-19 08:55



Es gibt schon einige gute Antworten. Ich werde mich konzentrieren hauptsächlich auf was ich denke, dass sie fehlen - eine Erklärung der "Nachteile" mit der Kopie-und-Tausch-Idiom ....

Was ist das Kopier-und-Tausch-Idiom?

Eine Möglichkeit, den Zuweisungsoperator im Sinne einer Tauschfunktion zu implementieren:

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

Die Grundidee ist, dass:

  • Der am meisten fehleranfällige Teil der Zuordnung zu einem Objekt besteht darin sicherzustellen, dass alle Ressourcen, die der neue Zustand benötigt, erfasst werden (z. B. Speicher, Deskriptoren).

  • Diese Akquisition kann versucht werden Vor Modifizieren des aktuellen Zustands des Objekts (d. h. *this) wenn eine Kopie des neuen Wertes erstellt wird, weshalb rhs ist akzeptiert nach Wert (d. h. kopiert) statt durch Bezugnahme

  • den Zustand der lokalen Kopie austauschen rhs und *this ist gewöhnlich relativ einfach, ohne potentielle Fehler / Ausnahmen zu tun, da die lokale Kopie hinterher keinen bestimmten Zustand benötigt (sie braucht nur einen Zustand, in dem der Destruktor ausgeführt werden kann, ähnlich wie bei einem Objekt) gerührt von in> = C ++ 11)

Wann sollte es verwendet werden? (Welche Probleme löst es? [/erstellen]?)

  • Wenn Sie möchten, dass der zugewiesene nicht von einer Zuweisung betroffen wird, die eine Ausnahme auslöst, vorausgesetzt, Sie haben oder können einen schreiben swap mit starker Ausnahmegarantie, und im Idealfall eine, die nicht scheitern kann /throw.. †

  • Wenn Sie eine saubere, leicht verständliche und robuste Möglichkeit suchen, den Zuweisungsoperator in Form eines (einfacheren) Kopierkonstruktors zu definieren, swap und Destruktorfunktionen.

    • Die Selbstzuweisung als Kopie-und-Austausch vermeidet häufig übersehene Randfälle.

  • Wenn eine Leistungseinbuße oder eine momentan höhere Ressourcennutzung, die durch ein zusätzliches temporäres Objekt während der Zuweisung verursacht wird, für Ihre Anwendung nicht wichtig ist. ⁂

swap Werfen: Es ist im Allgemeinen möglich, Datenelemente zuverlässig zu vertauschen, die die Objekte nach Zeiger verfolgen, aber Nicht-Zeiger-Datenelemente, die keinen auswurffreien Austausch haben oder für die das Austauschen implementiert werden muss X tmp = lhs; lhs = rhs; rhs = tmp; und Kopier-Konstruktion oder Zuordnung kann werfen, immer noch das Potenzial zu scheitern einige Daten Mitglieder vertauscht und andere nicht. Dieses Potenzial gilt auch für C ++ 03 std::stringJames kommentiert eine andere Antwort:

@wilhelmelt: 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 Exceptions werfen. - James McNellis 22. Dezember 10 um 15:24 Uhr


‡ Eine Zuweisungsoperatorimplementierung, die bei Zuweisung von einem bestimmten Objekt sinnvoll erscheint, kann leicht für die Selbstzuweisung fehlschlagen. Während es unvorstellbar erscheinen mag, dass Client-Code sogar eine Selbstzuweisung versuchen würde, kann dies relativ leicht während Algo-Operationen auf Containern passieren, mit x = f(x); Code wo f ist (vielleicht nur für einige #ifdef Zweige) ein Makro ala #define f(x) x oder eine Funktion, die eine Referenz zurückliefert xoder sogar (wahrscheinlich ineffizienter aber prägnanter) Code wie x = c1 ? x * 2 : c2 ? x / 2 : x;). Beispielsweise:

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 lösche der obige Code x.p_;, Punkte p_ in einem neu zugewiesenen Heap - Bereich, dann versucht, das zu lesen nicht initialisiert Daten darin (Undefined Behavior), wenn das nicht zu komisch ist, copy versucht eine Selbstaufgabe für jedes gerade zerstörte "T"!


⁂ Das Copy-and-Swap-Idiom kann zu Ineffizienzen oder Einschränkungen aufgrund der Verwendung eines zusätzlichen temporären Parameters führen (wenn der Parameter des Operators copy-konstruiert ist):

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

Hier, eine handschriftliche Client::operator= könnte prüfen, ob *this ist bereits mit demselben Server verbunden wie rhs (vielleicht einen "reset" -Code senden, wenn nützlich), während der copy-and-swap-Ansatz den Kopierkonstruktor aufrufen würde, der wahrscheinlich geschrieben würde, um eine eigene Socket-Verbindung zu öffnen, und dann den ursprünglichen zu schließen. Dies könnte nicht nur eine Remotenetzwerkinteraktion anstelle einer einfachen prozessinternen Variablenkopie bedeuten, sondern auch Client- oder Serverlimits für Socket-Ressourcen oder Verbindungen. (Natürlich hat diese Klasse eine ziemlich schreckliche Schnittstelle, aber das ist eine andere Sache ;-P).


32
2018-03-06 14:51



Diese Antwort ist eher eine Ergänzung und eine leichte Modifikation der obigen Antworten.

In einigen Versionen von Visual Studio (und möglicherweise anderen Compilern) gibt es einen Fehler, der wirklich nervig ist und keinen Sinn ergibt. Also, wenn Sie Ihre definieren / definieren swap Funktion wie folgt:

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

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

}

... der Compiler wird dich anschreien, wenn du das anrufst swap Funktion:

enter image description here

Das hat etwas mit einem zu tun friend Funktion wird aufgerufen und this Objekt wird als Parameter übergeben.


Ein Weg um dies zu nutzen ist nicht zu benutzen friend Stichwort und neu definieren swap Funktion:

void swap(A& other) {

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

}

Dieses Mal können Sie einfach anrufen swap und hineingehen otherund macht damit den Compiler glücklich:

enter image description here


Schließlich nicht brauchen verwenden a friend Funktion zum Tauschen von 2 Objekten. Es macht genauso viel Sinn zu machen swap eine Mitgliedsfunktion, die eins hat other Objekt als Parameter.

Sie haben bereits Zugriff auf this Objekt, also ist das Übergeben als Parameter technisch überflüssig.


19
2017-09-04 04:50



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

Betrachten wir zur Konkretheit einen Container std::vector<T, A>, woher A ist ein Stateful-Allocator-Typ und wir vergleichen 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 ist zu geben a der Staat das b hatte anfangs. Es gibt jedoch eine versteckte Frage: Was passiert, wenn a.get_allocator() != b.get_allocator()? Die Antwort ist: Es kommt darauf an. Lass uns schreiben AT = std::allocator_traits<A>.

  • Ob AT::propagate_on_container_move_assignment ist std::true_type, dann fm weist den Zuordner von neu zu a mit dem Wert von b.get_allocator()sonst geht es nicht, und a verwendet weiterhin seinen ursprünglichen Zuordner. In diesem Fall müssen die Datenelemente einzeln ausgetauscht werden, da die Speicherung von a und b ist nicht kompatibel.

  • Ob AT::propagate_on_container_swap ist std::true_type, dann fs tauscht Daten und Zuweiser in der erwarteten Weise.

  • Ob AT::propagate_on_container_swap ist std::false_typeDann brauchen wir einen dynamischen Check.

    • Ob a.get_allocator() == b.get_allocator(), dann verwenden die zwei Container einen kompatiblen Speicher, und das Tauschen erfolgt in der üblichen Weise.
    • Wie auch immer, wenn a.get_allocator() != b.get_allocator()hat das Programm undefiniertes Verhalten (Vgl. [container.voraussetzungen.general / 8].

Das Ergebnis ist, dass das Auslagern zu einer nicht-trivialen Operation in C ++ 11 geworden ist, sobald Ihr Container stateful allocators unterstützt. Das ist ein etwas "fortgeschrittener Anwendungsfall", aber es ist nicht ganz unwahr- scheinlich, da Verschiebungsoptimierungen in der Regel erst dann interessant werden, wenn Ihre Klasse eine Ressource verwaltet und Speicher eine der beliebtesten Ressourcen ist.


10
2018-06-24 08:16