std::vector push_back() destruktor umgehen



  • Dobi schrieb:

    kralo9 schrieb:

    Mit Pointer will ich nicht arbeiten. Auch nicht mit Smart-Pointer.

    kralo9 schrieb:

    Ich implementiere eine Klasse, die ähnlich wie die Smart-Pointer mitzählt, wie oft die erbende Klasse existiert.

    Nette Übung, aber warum das Rad neu erfinden?

    Kannst du mir ein Beispiel schreiben, das mit Smart-Pointern arbeitet, ohne, dass ich mit Pointer arbeite? Ich möchte nicht object->foo() haben, sondern object.foo()



  • Das sieht ziemlich umständlich aus. Ich würd einen mutable unsigned *counter; in die Klasse packen und entsprechend updaten. Auf keinen Fall würd ich Linearzeitalgorithmen (oder überhaupt alles über O(1)) bei jeder Kopie haben wollen.



  • Bashar schrieb:

    Das sieht ziemlich umständlich aus. Ich würd einen mutable unsigned *counter; in die Klasse packen und entsprechend updaten. Auf keinen Fall würd ich Linearzeitalgorithmen (oder überhaupt alles über O(1)) bei jeder Kopie haben wollen.

    Den letzten Satz verstehe ich nicht...

    Meinst du:

    const unsigned int& getCount() const {
    	update();
    	return _counter;
    }
    void update() const {
    	_counter = ReferenceMaster::getCountById(_id);
    }
    
    und
    
    mutable unsigned int _counter;
    

    Edit: PS: Hab ich denn noch einen Fall übersehen, in dem ein weiteres Objekt erstellt werden kann, außer ctor, copyctor, operator= ??



  • kralo9 schrieb:

    Bashar schrieb:

    Das sieht ziemlich umständlich aus. Ich würd einen mutable unsigned *counter; in die Klasse packen und entsprechend updaten. Auf keinen Fall würd ich Linearzeitalgorithmen (oder überhaupt alles über O(1)) bei jeder Kopie haben wollen.

    Den letzten Satz verstehe ich nicht...

    Damit ist gemeint, dass ich nicht will, dass bei jeder Kopie erstmal irgendwelche potentiell ewig langen Listen abgesucht werden, um da IDs einzutragen und dergleichen.

    Meinst du:

    Nein, einen Zeiger. Hatte ich doch so geschrieben.

    Jedes frei erzeugte Objekt erstellt sich einen Counter (counter = new unsigned(1)). Bei einer Kopie wird der Zeiger darauf kopiert und der Zähler inkrementiert. Dadurch sehen alle Objekte, die den gleichen Zeiger haben, den geänderten Zähler. Im Destruktor wird der Zähler dekrementiert. Wenn wir bei 0 sind, wird der Zeiger deletet. Der Rest ist dem Leser zur Übung überlassen. 🙂

    Achso, echte Kritik ist willkommen, ich hab das so noch nie implementiert, aber ich denke so geht es.



  • kralo9 schrieb:

    Edit: PS: Hab ich denn noch einen Fall übersehen, in dem ein weiteres Objekt erstellt werden kann, außer ctor, copyctor, operator= ??

    Die move-Varianten. Sollten insgesamt 5 sein.

    Bashar schrieb:

    Jedes frei erzeugte Objekt erstellt sich einen Counter (counter = new unsigned(1)). Bei einer Kopie wird der Zeiger darauf kopiert und der Zähler inkrementiert. Dadurch sehen alle Objekte, die den gleichen Zeiger haben, den geänderten Zähler. Im Destruktor wird der Zähler dekrementiert. Wenn wir bei 0 sind, wird der Zeiger deletet. Der Rest ist dem Leser zur Übung überlassen. 🙂

    Achso, echte Kritik ist willkommen, ich hab das so noch nie implementiert, aber ich denke so geht es.

    Genau so funktioniert std::shared_ptr, wird also so falsch nicht sein.



  • kralo9 schrieb:

    Dobi schrieb:

    kralo9 schrieb:

    Mit Pointer will ich nicht arbeiten. Auch nicht mit Smart-Pointer.

    kralo9 schrieb:

    Ich implementiere eine Klasse, die ähnlich wie die Smart-Pointer mitzählt, wie oft die erbende Klasse existiert.

    Nette Übung, aber warum das Rad neu erfinden?

    Kannst du mir ein Beispiel schreiben, das mit Smart-Pointern arbeitet, ohne, dass ich mit Pointer arbeite? Ich möchte nicht object->foo() haben, sondern object.foo()

    Was ist so schlimm an "->"?



  • Wow. Mit einem Pointer als Zähler ist es erstaunlich einfach 😃 Ich denke immer viel zu kompliziert...

    Der Counter:

    class SimpleReferenceCounter {
    public:
    	SimpleReferenceCounter() :
    	_counter(new unsigned int(1)) {
    	}
    	~SimpleReferenceCounter() {
    		--(*_counter);
    		checkForDelete();
    	}
    	SimpleReferenceCounter(const SimpleReferenceCounter& other) {
    		_counter = other._counter;
    		++(*_counter);
    	}
    	void operator=(const SimpleReferenceCounter& other) {
    		--(*_counter);
    		checkForDelete();
    		_counter = other._counter;
    		++(*_counter);
    	}
    
    	//For Testing:
    	const unsigned int& getCount() const {
    		return *_counter;
    	}
    
    protected:
    	bool lastReference() const {
    		return (*_counter == 1);
    	}
    
    private:
    
    	void checkForDelete() {
    		if(*_counter == 0)
    			delete _counter;
    	}
    
    	unsigned int* _counter;
    };
    

    Da es anscheinend 5 Funktionen gibt, wenn man die Move-Varianten mitzählt, fehlen noch 2. Wie sehen die move operationen aus?



  • Suuuuuuper, du implementierst Referencecounting wie bspw shared_ptr oder aehnliches ... voellig unnoetig und unverstaendlich.

    class MyClass : public ReferenceCounter{ 
     ... 
    }
    

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

    Ich möchte nicht object->foo() haben, sondern object.foo()

    Haeh?

    (*ptr).foo()
    


  • 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.

    Stattdessen könntest du mir sagen, wie die move operationen aussehen. Außerdem ist mein Counter nicht von C++11 abhänging (oder C++0x, nicht sicher)


  • Mod

    Hier mal zum Vergleich eine Variante ohne Referenzcounter

    #include <cstddef>
    #include <cassert>
    #include <iostream>
    
    class counter_mixin
    {
    public:
        counter_mixin() noexcept
            : prev(this), next(this)
        {}
        counter_mixin(const counter_mixin& other) noexcept
            : prev(&other), next(other.next)
        {
            prev->next = this;
            next->prev = this;
        }
        counter_mixin(counter_mixin&& other) noexcept
            : counter_mixin(other)
        {
            other.make_unique();
        }
        counter_mixin& operator=(const counter_mixin& rhs) noexcept
        {
             counter_mixin(rhs).swap(*this);
             return *this;
        }
        counter_mixin& operator=(counter_mixin&& rhs) noexcept
        {
            if ( this != &rhs ) // destruktives self-move ist in diesem Fall schlecht, geht wahrscheinlich auch eleganter
            {
                *this = rhs;
                rhs.make_unique();
            }
            return *this;
        }
        ~counter_mixin() noexcept
        {
            prev->next = next;
            next->prev = prev;
        }
        void swap(counter_mixin& other) noexcept
        {
            intersect(this,&other);
            intersect(next,other.next);
        }
        void make_unique() noexcept
        {
            prev->next = next;
            next->prev = prev;
            next = prev = this;
        }
        bool is_unique() const noexcept
        {
            return next == this;
        }
        size_t count() const noexcept
        {
            size_t x = 1;
            for ( auto p = next; p != this; p = p->next )
                ++x;
            return x;
        }
    private:
        void link_forward(const counter_mixin* other) const noexcept
        {
            other->prev = this;
            this->next = other;
        }
        void link_backward(const counter_mixin* other) const noexcept
        {
            other->next = this;
            this->prev = other;
        }
        friend void intersect(const counter_mixin* a, const counter_mixin* b)
        {
            const counter_mixin* a_prev = a->prev;
            a->link_backward( b->prev );
            b->link_backward( a_prev );
        }
        mutable const counter_mixin* prev;
        mutable const counter_mixin* next;
    };
    
    class MyClass : public counter_mixin {
    public:
        MyClass() :
        _killWhenLast(12345) {
            std::cout << "MyClass Constructor\n";
        }
        ~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 = m;
            assert(m2.count() == 2);
            assert(m.count() == 2);
    
            MyClass m3(m);
            assert(m.count() == 3);
            assert(m2.count() == 3);
            assert(m3.count() == 3);
        }
        assert(m.count() == 1);
    }
    


  • 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 😃

    Edit: Mir kommt gerade die Erkenntnis, dass move auch c++11 ist. Kann ich die C++ Version mit einem präprozessor abfragen und wie?



  • Suchst du nicht genau einen moveable-type?


  • 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?


Anmelden zum Antworten