Warum ist der Destruktor-Aufruf nach dem std::move notwendig?

Lesezeit: 6 Minuten

Benutzer-Avatar
cvomake

In der C++-Programmiersprache Edition 4 gibt es ein Beispiel einer Vektorimplementierung, siehe relevanten Code am Ende der Nachricht.

uninitialized_move() initialisiert neue T-Objekte in den neuen Speicherbereich, indem sie aus dem alten Speicherbereich verschoben werden. Dann ruft es den Destruktor für das ursprüngliche T-Objekt auf, das Objekt, aus dem es verschoben wurde. Warum ist der Destruktor-Aufruf in diesem Fall notwendig?

Hier ist mein unvollständiges Verständnis: Das Verschieben eines Objekts bedeutet, dass das Eigentum an den Ressourcen, die dem verschobenen Objekt gehören, auf das verschobene Objekt übertragen wird. Die Reste im verschobenen Objekt sind einige mögliche Mitglieder von eingebauten Typen, die nicht zerstört werden müssen, sie werden freigegeben, wenn die vector_base b den Gültigkeitsbereich verlässt (innerhalb Reservieren()nach dem Tauschen() Anruf). Alle Zeiger im verschobenen Objekt müssen auf nullptr gesetzt werden, oder es wird ein Mechanismus verwendet, um den Besitz des verschobenen Objekts auf diesen Ressourcen zu löschen, damit wir sicher sind, warum dann den Destruktor für ein erschöpftes Objekt aufrufen, wenn die “vector_base b ” Der Destruktor wird den Speicher sowieso freigeben, nachdem der Austausch abgeschlossen ist?

Ich verstehe die Notwendigkeit, den Destruktor in den Fällen explizit aufzurufen, in denen er aufgerufen werden muss, weil wir etwas zu zerstören haben (z. B. Elemente löschen), aber ich sehe seine Bedeutung nach std::move + deallocation von vector_base nicht. Ich habe einige Texte im Netz gelesen und sehe den Destruktoraufruf des verschobenen Objekts als Signal (an wen oder was?), dass die Lebensdauer des Objekts abgelaufen ist.

Bitte klären Sie mich auf, welche sinnvolle Arbeit der Destruktor noch erledigen muss. Vielen Dank!

Das folgende Code-Snippet stammt von hier http://www.stroustrup.com/4th_printing3.html

template<typename T, typename A>
void vector<T,A>::reserve(size_type newalloc)
{
    if (newalloc<=capacity()) return;                   // never decrease allocation
    vector_base<T,A> b {vb.alloc,size(),newalloc-size()};   // get new space
    uninitialized_move(vb.elem,vb.elem+size(),b.elem);  // move elements
    swap(vb,b);                                 // install new base 
} // implicitly release old space

template<typename In, typename Out>
Out uninitialized_move(In b, In e, Out oo)
{
    using T = Value_type<Out>;      // assume suitably defined type function (_tour4.iteratortraits_, _meta.type.traits_)
    for (; b!=e; ++b,++oo) {
        new(static_cast<void*>(&*oo)) T{move(*b)};  // move construct
        b->~T();                                // destroy
    }
    return oo;       
}

  • Dies ist eine generische Implementierung, Sie wissen es jetzt nicht einmal T{move(*b)} ruft einen Bewegungskonstruktor auf.

    – dyp

    14. Dezember 2013 um 22:38 Uhr

  • Denken Sie nicht an einen Umzug zu viel. Bewegen ist in erster Näherung wie Kopieren, und die Welt spielt immer noch nach denselben Regeln.

    – Kerrek SB

    14. Dezember 2013 um 22:45 Uhr

  • 🙂 aber verschieben ist sehr wichtig, gerade weil es nicht kopieren ist, was in manchen Fällen zu teuer ist, also muss ich es anders als kopieren betrachten. Beim Kopieren kann ich entscheiden, ob ich das Original fallen lassen möchte oder nicht, beim Bewegen sage ich sozusagen: “Ich bin jetzt der neue Besitzer Ihrer Sachen und Sie sind erschöpft.”

    – cvomake

    14. Dezember 2013 um 22:53 Uhr

Benutzer-Avatar
Dietmar Kühl

Das Bewegen von einem Objekt bedeutet nur, dass das Objekt bewegt wurde könnte spendet seinen Mut, um in einem anderen lebenden Objekt weiterzuleben, kurz bevor es ist [probably] wird sterben. Beachten Sie jedoch, dass das Objekt nicht tot ist, nur weil es seine Eingeweide gespendet hat! Tatsächlich kann es durch ein anderes Spenderobjekt wiederbelebt werden und von den Eingeweiden dieses Objekts leben.

Außerdem ist es wichtig zu verstehen, dass Zugkonstruktion oder Zugzuweisung tatsächlich Kopien sein können! In der Tat, sie Wille Kopien sein, wenn der Typ, der verschoben wird, zufällig ein Pre-C++11-Typ mit einem Kopierkonstruktor oder einer Kopierzuweisung ist. Selbst wenn eine Klasse einen Move-Konstruktor oder eine Move-Zuweisung hat, kann sie entscheiden, dass sie ihre Eingeweide nicht auf das neue Objekt verschieben kann, zB weil die Allokatoren nicht übereinstimmen.

In jedem Fall kann ein verschobenes Objekt immer noch Ressourcen haben oder Statistiken oder was auch immer aufzeichnen müssen. Um das Objekt loszuwerden, muss es zerstört werden. Abhängig von den Verträgen der Klasse kann es nach dem Auszug sogar einen definierten Zustand haben und ohne weiteres einer neuen Verwendung zugeführt werden.

  • @cvomake: Eine Kopie ist eine gültige Bewegungskonstruktion, solange die Bewegung nicht garantiert, dass Zeiger oder Verweise auf das alte Objekt gültig bleiben, solange das neue Objekt gültig bleibt. Ein Umzug tut es nicht garantieren, dass alle Ressourcen übertragen werden. Es zeigt lediglich an, dass es sicher ist, Ressourcen zu übertragen, und daher zielen viele Klassen eher darauf ab, Ressourcen bei Bewegungen zu übertragen als zu kopieren.

    – Dietmar Kühl

    14. Dezember 2013 um 23:32 Uhr

  • Bewegtes Objekt ist wie Zombie, du musst es noch töten 🙂

    – Amis

    15. Dezember 2013 um 1:28 Uhr

  • @DyP: Deine Aussage versuche ich schon seit Jahren zu diskreditieren. Bei einem verschobenen Objekt müssen noch alle seine Invarianten intakt sein. Das Einzige, worauf sich ein Client eines Objekts, aus dem es verschoben wurde, verlassen kann, ist, dass sich das Objekt, aus dem es verschoben wurde, in einem gültigen (intakte Invarianten), aber nicht spezifizierten Zustand befindet. Wenn Sie nun der Autor dieser Klasse sind, können Sie ihre Invarianten beliebig definieren. Vielleicht hat Ihre Klasse eine Invariante, die einem verschobenen Wert entspricht, der nur zerstört oder zugewiesen werden kann.

    – Howard Hinnant

    15. Dezember 2013 um 1:44 Uhr

  • Eine allgemeinere Art, darüber nachzudenken, ist, dass ein beliebiger Member aufgerufen werden kann, der keine Vorbedingungen hat. Wenn alle Ihre Mitglieder die Voraussetzung haben, dass sich der Wert nicht in einem verschobenen Zustand befindet, dann ist das Ihr Vorrecht. Aber das ist keine Voraussetzung der Bewegungssemantik. std-definierte Typen wie vector<T> kann jedes Mitglied angerufen werden, das keine Vorbedingungen hat. Das beinhaltet empty(), clear() und size().

    – Howard Hinnant

    15. Dezember 2013 um 1:46 Uhr

  • @Barry: Studieren Sie die Implementierung des Generals std::swap Algorithmus mit Ihrer Frage im Hinterkopf. Das soll nicht heißen, dass “destruktive Bewegungssemantik” nicht nützlich wäre. Ich bin sicher, es würde. Aber wenn ich mich zwischen „konstruktiver Bewegungssemantik“ und „destruktiver Bewegungssemantik“ entscheiden müsste, würde ich erstere wählen, und genau diese Wahl hatten wir vor einem Jahrzehnt. Sehen open-std.org/jtc1/sc22/wg21/docs/papers/2002/… für ein paar weitere Details zu dieser Wahl.

    – Howard Hinnant

    5. März 2016 um 15:21 Uhr

In seinem Vorschlag zu Implementieren Sie Bewegungssemantik durch Rvalue-ReferenzenHoward Hinant erwog den Vorstoß Zerstörerische Bewegung zu. Aber wie er im selben Artikel betonte, befasst sich eine Verschiebungsoperation im Wesentlichen gleichzeitig mit Objekten (Quelle und Ziel) im Übergangszustand – sogar in Single-Thread-Anwendungen. Das Problem bei der destruktiven Bewegung besteht darin, dass entweder die Quelle oder das Ziel einen Zustand erfahren, in dem ein abgeleiteter Unterteil eines Objekts konstruiert wird, während ein Basis-Unterteil zerstört oder nicht initialisiert wird. Dies war vor dem Umzugsvorschlag nicht möglich und ist noch nicht akzeptabel. Die andere verbleibende Wahl bestand also darin, das verschobene Objekt in einem gültigen leeren Zustand zu belassen und den Destruktor den gültigen leeren Zustand entfernen zu lassen. Für viele praktische Zwecke ist das Zerstören eines gültigen leeren Zustands ein Noop; Aber es ist nicht möglich, für alle Fälle zu verallgemeinern. Das verschobene Objekt soll also entweder zerstört oder zugewiesen (verschieben oder kopieren) werden.

1019040cookie-checkWarum ist der Destruktor-Aufruf nach dem std::move notwendig?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy