std::vector push_back() destruktor umgehen


  • Mod

    kralo9 schrieb:

    Könntest du bitte vor die Operationen deines "Reference-Watcher's" (so nenn ich das mal) als Kommentar schreiben, welche Operation das ist? Also ctor und copy-ctor erkenne ich auch so, aber bei dem move dingern 😃

    counter_mixin bildet eine zyklische, doppelt-verkette Liste ohne zusätzliches Datum oder einen speziellen Ankerknoten (da keine leeren Listen unterstützt werden müssen). Ggf. müsstest du konkreter Fragen.

    kralo9 schrieb:

    Kann ich die C++ Version mit einem präprozessor abfragen und wie?

    __cplusplus >= 201103L
    Wird allerdings von älteren Compilerversionen mit partieller C++11-Unterstützung z.T. nicht gesetzt.



  • camper schrieb:

    kralo9 schrieb:

    Könntest du bitte vor die Operationen deines "Reference-Watcher's" (so nenn ich das mal) als Kommentar schreiben, welche Operation das ist? Also ctor und copy-ctor erkenne ich auch so, aber bei dem move dingern 😃

    counter_mixin bildet eine zyklische, doppelt-verkette Liste ohne zusätzliches Datum oder einen speziellen Ankerknoten (da keine leeren Listen unterstützt werden müssen). Ggf. müsstest du konkreter Fragen.

    Ah nein ich habe das anders gemeint. Ich wollte wissen, welche Funktionen bei welchen Operationen aufgerufen werden. Also, welche Funktion tritt in Kraft, wenn ich ein std::move mache.


  • Mod

    kralo9 schrieb:

    Ah nein ich habe das anders gemeint. Ich wollte wissen, welche Funktionen bei welchen Operationen aufgerufen werden. Also, welche Funktion tritt in Kraft, wenn ich ein std::move mache.

    std::move löst selbst überhaupt keinen weiteren Funktionsaufruf aus. Eine Initialisierung od. Zuweisung danach führt allerdings zum Aufruf des Move-ctor bzw. move-Zuweisungsoperators (sofern diese vorhanden sind).

    class MyClass : public counter_mixin {
    public:
        MyClass() :
        _killWhenLast(12345) {
            std::cout << "MyClass Constructor\n";
        }
        MyClass(const MyClass&) = default; // ruft copy-ctor des mixins auf
        MyClass(MyClass&&) = default;      // ruft move-ctor des mixins auf
        MyClass& operator=(const MyClass&) = default; // ruft copy-op des mixins auf
        MyClass& operator=(MyClass&&) = default;      // ruft move-op des mixins auf
        ~MyClass() {
            std::cout << "MyClass Destructor\n";
            if(is_unique()){
                _killWhenLast = 0;
            }
            std::cout << "_killWhenLast: " << _killWhenLast << "\n";
        }
    private:
        int _killWhenLast;
    };
    
    int main()
    {
        MyClass m;
        assert(m.count() == 1);
        {
            MyClass m2;
            assert(m2.count() == 1);
    
            m2 = std::move(m);  // ruft den move-Zuweisungsoperator auf
            assert(m2.count() == 1);
            assert(m.count() == 1); // m ist nach dem move "leer"
    
            MyClass m3(std::move(m2)); // ruft den move-ctor auf
            assert(m.count() == 1);
            assert(m2.count() == 1);
            assert(m3.count() == 1);
        }
        assert(m.count() == 1);
    }
    


  • Ah danke. Jetzt weiß ich den Move-Ctor und Move-Zuweiser 🙂

    Mir fällt es etwas schwer zu verstehen, was eine "leere" Klasse ist, nachdem sie bewegt wurde.
    Was ist m2 nach

    m = std::move(m2);
    

    ?

    Edit: Hat das doppelte Ampersand (&) eine wirkliche bedeutung oder ist das nur da, um den move zu definieren?
    Edit2: Wird bei oben genanntem move der destruktor von m2 aufgerufen?


  • Mod

    Mir fällt es etwas schwer zu verstehen, was eine "leere" Klasse ist, nachdem sie bewegt wurde.

    Sie ist nicht leer, sondern ihre Ressourcen wurden weitergegeben. Meistens ist sie dann entweder einfach ungültig, oder ihr Status ist nicht weiter definiert, oder sie ist einfach im "Standardzustand" (also ein Container ist im "Standardzustand" leer, ohne Elemente - welchen Status der Allokator hat, hängt von anderen Sachen ab).

    Edit: Hat das doppelte Ampersand (&) eine wirkliche bedeutung oder ist das nur da, um den move zu definieren?

    Ja, damit qualifizierst du einen Typen um eine sog. rvalue-Referenz zu bekommen.

    int a; // Ein Objekt
    int& a_ref = a; // Eine lvalue-Referenz auf int
    int&& a_rref = std::move(a); // eine rvalue-Referenz auf int. Bei Skalaren ist moven sinnlos, da diese keine Ressourcen halten.
    


  • lvalue und rvalue...

    x = 3;
    

    x ist lvalue und 3 ist rvalue, richtig?


  • Mod

    Ja, richtig! 3 ist ein Literal. Ein (normales) Literal ist immer ein rvalue.
    Und x muss ein lvalue sein.



  • Haha jaa 😃

    Aber ich verstehe nicht den zusammenhang mit Klassen.

    Und:
    Gibt es einen Unterschied zwischen folgenden Funktionen?:

    void MyClass::operator=(const MyClass& other){
    ...
    }
    MyClass& MyClass::operator=(const MyClass& other){
    ...
    return [MyClass];
    }
    

    Und woher weiß der Compiler, welchen operator= ich meine?


  • Mod

    void MyClass::operator=(const MyClass& other) // Sollte wohl MyClass& zurückgeben
    {
    ...
    }
    MyClass& MyClass::operator=(const MyClass& other)
    {
    ...
    return [MyClass]; // Was ist das? Du meinst wohl *this...
    }
    

    Und woher weiß der Compiler, welchen operator= ich meine?

    Du darfst so gar nicht überladen. Funktionen, die sich nur ihn ihrem Rückgabewert unterscheiden, können nicht überladen werden.



    • schrieb:
    void MyClass::operator=(const MyClass& other) // Sollte wohl MyClass& zurückgeben
    {
    ...
    }
    MyClass& MyClass::operator=(const MyClass& other)
    {
    ...
    return [MyClass]; // Was ist das? Du meinst wohl *this...
    }
    

    Und woher weiß der Compiler, welchen operator= ich meine?

    Du darfst so gar nicht überladen. Funktionen, die sich nur ihn ihrem Rückgabewert unterscheiden, können nicht überladen werden.

    Also sind beide Funktionen identisch?
    [MyClass] sollte ein platzhalter für ein objekt vom typ MyClass sein...

    Edit: Wieso ruft das den CopyCtor auf?

    MyClass m4(std::move(m2));
    

  • Mod

    Weil kein Move-Konstruktor definiert ist.



  • kralo9 schrieb:

    knivil schrieb:

    Nun, ob MyClass wirklich ein ReferenceCounter ist, bezweifle ich. Die is_a-Beziehung kann schwer gerechtfertigt werden.

    Da gebe ich dir Recht. Es sollte eine Has-A Beziehung sein. Ansonsten gebe ich dir kein Recht.

    Has-A löst auch das Problem mit der Namensreservierung der Funktionen.

    Du könntest den relativ seltenen Weg der privaten Vererbung wählen. Die drückt eine "is implemented by"-Beziehung aus. Und da du ja nicht per Basisklassenzeiger oder -referenz auf deine Objekte zugreifst brauchst du in Richtung ReferenceCounter auch keine Polymorphie haben.

    Dann:

    void MyClass::operator=(const MyClass& other)
    {
       // ...
    }
    MyClass& MyClass::operator=(const MyClass& other)
    {
        return *this;
    }
    

    Die void Methode ist nicht sehr sinnvoll, denn dadurch werden solche Konstrukte nicht mehr möglich sein:

    MyClass m1, m2, m3;
    m1 = m2 = m3 = MyClass();
    if ( (m1 = 2*m2) == m3 ) {}
    

    Zweitens gibt es das wunderschnöde Copy&Swap Pattern:

    MyClass::MyClass(MyClass const& m)
    {
        // Copy-C-Tor Implementieren
    }
    
    MyClass& MyClass::operator=(MyClass m)
    {
        this->swap(m);
        return *this;
    }
    
    void MyClass::swap(MyClass & other)
    {
        // swap implementieren
    }
    


  • Ich glaube die has-a Beziehung ist ganz ok. Danke dennoch für das Erwähnen der is-implemented-by beziehung.

    Ich möchte nochmal die Frage stellen, ob std::move(m123) den Destruktor von m123 aufruft.

    Ich zähl nochmal kurz auf:
    Ctor
    CopyCtor
    operator=
    MoveCtor
    Move-operator=
    (Swap-operator=)

    Gibt es noch was?
    Neue Objekte, d.h. eine Erhöhung des Counters, entsteht nur bei den ersten 3 Möglichkeiten, richtig?



  • Nein, das ist falsch.

    Neue Objekte werden bei allen 3 Konstruktoren erzeugt: Ctor,Copy-Ctor, Move-Ctor.

    Die Assignment-Operatoren löschen zwar alte Ressourcen, aber erzeugen keine neuen Objekte.

    Und die Swap-Methode ist eine normale Methode. Infos

    Move ruft keinen Destruktor auf. Move ruft den Move Konstruktor oder Zuweisungsoperator auf. Da weisst du dann, dass du das übergebene Objekt in einen Zustand bringen kannst, der nicht mehr interessiert, da es eh nicht gebraucht wird. (Das stimmt so natürlich nicht, jedes lebendige Objekt sollte immer in einem gültigen Zustand sein. Vielleicht wäre Initial- oder Leerzustand treffender.)


Anmelden zum Antworten