std::vector push_back() destruktor umgehen



  • Hallo liebe Community,

    vielleicht ist der Titel etwas schlecht gewählt, aber ich werde mir Mühe geben, sodass alle mein Problem verstehen:

    Angenommen ich habe folgenden Vektor:

    std::vector<MyClass> myClasses;
    

    Und der Destruktor von MyClass sorgt für Aufräumarbeiten, sodass die sagen wie mal Zeiger ungültig sind. Bsp:

    MyClass::~MyClass(){
    if(_pSomething != nullptr)
    delete _pSomething;
    _pSomething = nullptr;
    }
    

    (Eigentlich arbeite ich mit OpenGL und der "Pointer" ist ein Integer als ID für eine Textur im Grafikspeicher)

    Ich will die Aufräumarbeiten verhindern, wenn das Objekt in irgendeiner Art dupliziert wurde. In diesem Beispiel temporär durch den Aufruf von

    myClasses.push_back(...)
    

    Jetzt gibt es zwei Möglichkeiten:

    1. Ich habe einen Vektor mit Pointern und nicht mit Objekten, sodass sich nur die Pointer kopieren, zerstören und was auch immer, aber die Objekte unberührt bleiben. Aber das ist eine hässliche Lösung. Mit Pointer will ich nicht arbeiten. Auch nicht mit Smart-Pointer.

    2. Ich implementiere eine Klasse, die ähnlich wie die Smart-Pointer mitzählt, wie oft die erbende Klasse existiert. Dazu brauch ich eine Zahl die das repräsentiert und muss gewisse Funktionen überschreiben.
    Womit wir zu meiner Frage kommen: Welche Funktionen? Reicht operator= und der Kopier-Konstruktor?

    Ich möchte zum Schluss etwas in dieser Art haben:

    class MyClass : public ReferenceCounter{
    ...
    }
    

    Und im Destruktor:

    if(amIForeverAlone()) // :D
    cleanup();
    

    Über eine Antwort würde ich mich freuen 🙂

    P.S.: Ganz nebenbei: Wenn ich den Nummerncode eines Unicode-32 Zeichen haben will, dann ist doch folgendes richtig, oder?

    char32_t ch = 'ö';
    int code = static_cast<int>(ch);
    


  • Hab gerade wenig Zeit und es erklärt gleich sicher bereits jemand, aber schau Dir doch Mal shared_ptr an, das könnte das erledigen, was Du möchtest.

    Ach und es ist unsinnig auf nullptr zu prüfen. Ein delete nullptr; ist klar definiert und führt zu keinem Schaden. Am Besten aber eh unique_ptr für Deine Elemente verwenden.


  • Mod

    kralo9 schrieb:

    2. Ich implementiere eine Klasse, die ähnlich wie die Smart-Pointer mitzählt, wie oft die erbende Klasse existiert. Dazu brauch ich eine Zahl die das repräsentiert und muss gewisse Funktionen überschreiben.
    Womit wir zu meiner Frage kommen: Welche Funktionen? Reicht operator= und der Kopier-Konstruktor?

    Jeder Konstruktor (also Default- und copy/Move-ctor) sowie der Destruktor. Dier Regel der Drei greift hier nicht, weil die zusätzliche Funktionalität dieser Funktionen den Zustand des Objekts nicht berührt.

    template <typename T>
    struct counter_mixin
    {
    protected:
        counter_mixin() noexcept { ++counter; }
        counter_mixin(const counter mixin&) noexcept { ++counter; }
        counter_mixin(counter mixin&&) noexcept { ++counter; }
        ~counter_mixin() noexcept { --counter; }
        static int counter;
    };
    template <typename T>
    int counter_mixin<T>::counter = 0;
    


  • camper schrieb:

    template <typename T>
    struct counter_mixin
    {
    protected:
        counter_mixin() noexcept { ++counter; }
        counter_mixin(const counter mixin&) noexcept { ++counter; }
        counter_mixin(counter mixin&&) noexcept { ++counter; }
        ~counter_mixin() noexcept { --counter; }
        static int counter;
    };
    template <typename T>
    int counter_mixin<T>::counter = 0;
    

    Das macht für mich keinen Sinn, da der counter statisch ist. Ich möchte für jedes Objekt einen eigenen Counter und wenn möglich als geerbte Klasse. Ich versuche mich an einem Counter und werde ihn posten



  • Es ist bestimmt viel zu umständlich, aber es funktioniert für die Tests, die ich gemacht habe. Da fehlt bestimmt noch was oder jemand sieht eine Optimierungsmöglichkeit:

    Für der Counter:

    class BaseReferenceCounter {
    public:
    	virtual ~BaseReferenceCounter() {
    	}
    
    	virtual const unsigned int& getLastCount() const = 0;
    	virtual const unsigned int& getCount() = 0;
    	virtual const unsigned int& getId() const = 0;
    };
    
    class ReferenceMaster {
    public:
    	ReferenceMaster() = delete;
    
    	static unsigned int pullId() {
    		return _nextId++;
    	}
    	static void attachCounter(BaseReferenceCounter* counter) {
    		for(size_t i = 0; i < _referenceCounters.size(); ++i){
    			if(_referenceCounters[i] == counter){
    				return;
    			}
    		}
    
    		_referenceCounters.push_back(counter);
    	}
    	static void detachCounter(BaseReferenceCounter* counter) {
    		for(size_t i = 0; i < _referenceCounters.size(); ++i){
    			if(_referenceCounters[i] == counter){
    				_referenceCounters.erase(_referenceCounters.begin() + i);
    			}
    		}
    	}
    
    	static unsigned int getCountById(const unsigned int& id) {
    		unsigned int n = 0;
    		for(size_t i = 0; i < _referenceCounters.size(); ++i){
    			if(_referenceCounters[i]->getId() == id)
    				++n;
    		}
    		return n;
    	}
    
    private:
    	static unsigned int _nextId;
    	static std::vector<BaseReferenceCounter*> _referenceCounters;
    };
    unsigned int ReferenceMaster::_nextId = 0;
    std::vector<BaseReferenceCounter*> ReferenceMaster::_referenceCounters;
    
    class ReferenceCounter : public BaseReferenceCounter {
    public:
    	ReferenceCounter() :
    	_counter(1),
    	_id(ReferenceMaster::pullId()) {
    		ReferenceMaster::attachCounter(this);
    	}
    	~ReferenceCounter() {
    		ReferenceMaster::detachCounter(this);
    	}
    	ReferenceCounter(const ReferenceCounter& other) {
    		_counter = other.getLastCount();
    		_id = other.getId();
    		ReferenceMaster::attachCounter(this);
    	}
    	void operator=(const ReferenceCounter& other) {
    		ReferenceMaster::detachCounter(this);
    		_counter = other.getLastCount();
    		_id = other.getId();
    		ReferenceMaster::attachCounter(this);
    	}
    
    	const unsigned int& getLastCount() const {
    		return _counter;
    	}
    
    	const unsigned int& getCount() {
    		update();
    		return _counter;
    	}
    
    	const unsigned int& getId() const {
    		return _id;
    	}
    
    protected:
    
    	bool lastReference() {
    		return (getCount() == 1);
    	}
    
    private:
    	void update() {
    		_counter = ReferenceMaster::getCountById(_id);
    	}
    
    	unsigned int _counter;
    	unsigned int _id;
    };
    

    Die Klasse MyClass:

    class MyClass : public ReferenceCounter {
    public:
    	MyClass() :
    	_killWhenLast(12345) {
    		std::cout << "MyClass Constructor\n";
    	}
    	~MyClass() {
    		std::cout << "MyClass Destructor\n";
    		if(lastReference()){
    			_killWhenLast = 0;
    		}
    		std::cout << "_killWhenLast: " << _killWhenLast << "\n";
    	}
    private:
    	int _killWhenLast;
    };
    

    Der TestCode:

    MyClass m;
    assert(m.getCount() == 1);
    {
    	MyClass m2;
    	assert(m2.getCount() == 1);
    
    	m2 = m;
    	assert(m2.getCount() == 2);
    	assert(m.getCount() == 2);
    
    	MyClass m3(m);
    	assert(m.getCount() == 3);
    	assert(m2.getCount() == 3);
    	assert(m3.getCount() == 3);
    }
    assert(m.getCount() == 1);
    

    Die Ausgabe:

    MyClass Constructor
    MyClass Constructor
    MyClass Destructor
    _killWhenLast: 12345
    MyClass Destructor
    _killWhenLast: 12345
    MyClass Destructor
    _killWhenLast: 0
    

    Die Funktionen wie getCount() müsste ich noch umbennen, damit sie nicht Funktionen von MyClass blockieren, da der Name schon vergeben ist. Das ist wohl der Nachteil dabei.



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



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


Anmelden zum Antworten