Kopien und Zuweisungen von Klassenobjekten


  • Mod

    Ich kann euch grad nicht so ganz folgen...: Wann muss ich den Destruktor angeben, wann reicht der default-Destruktor? ^^

    Wenn irgendetwas bei der Zerstörung des Objekts passieren soll, definierst du einen Destruktor. Einfach, oder?

    Wenn von der Basisklasse abgeleitet wird, bekommt die Basisklasse einen virtuellen Destruktor.

    Nicht immer. Nur wenn das ganze polymorph sein soll.

    Jede abgeleitete Klasse hat ihre eigenen Zuweisungs und Kopierkonstruktoren, oder? Keine gemeinsame virtuelle?

    Interessanter Nebenfakt: Du kannst einen virtuellen Zuweisungsoperator deklarieren. Der Parameter muss immer gleichbleiben (daher musst du ggf. immer einen zweiten mit dem konkreten Typen definieren), die zurückgegebene Referenz darf aber immer den konkreten Typen haben (covariant return types).

    Von welchen Ausnahmen redet ihr? Ausnahmen zur Regel "Dreierregel immer anwenden" oder ist die Dreierregel jetzt wieder eine Ausnahme ....?

    Die Dreierregel ist keine Ausnahme, sondern eine immer zu berücksichtigende Faustregl. Wenn du ein wenig erfahrener wirst, passiert alles von selbst.



  • nwp3 schrieb:

    Lymogry schrieb:

    Wann muss ich den Destruktor angeben, wann reicht der default-Destruktor?

    Der Defaultkonstruktor tut genau gar nichts. Wenn das ok ist ist es ok, ansonsten halt nicht.

    falsch und nicht auf die quote zutreffend. der defaultkonstruktor ruft die defaultkonstruktoren von allen basisklassen und von allen membern auf (in dieser reihenfolge). der defaultdestruktor ruft die destruktoren von allen membern und von allen basisklassen auf (auch in dieser reihenfolge).



  • nwp3 schrieb:

    Lymogry schrieb:

    Anm.: Jede abgeleitete Klasse hat ihre eigenen Zuweisungs und Kopierkonstruktoren, oder? Keine gemeinsame virtuelle?

    Richtig. Das ist sogar ein ziemlich großes Problem. Ich erwarte dass das geändert wird.

    ein virtueller Copy-Konstruktor kann doch ganr nicht funktionieren oder? Die haben doch alle unterschiedliche Signaturen, was ja auch nötig ist, da man den exakten Typ der Kopie zur Compile-Zeit benötigt.


  • Mod

    Das ist sogar ein ziemlich großes Problem.

    Wo ist da ein großes Problem? Das lässt sich alles durch kleine Workarounds per virtuellem clone() o.ä. lösen.



  • ähm jungs..... 😃 ich hab mal den Test gemacht und den Zuweisungs- und Kopierkonstruktor auskommentiert: es funktioniert immernoch 😮

    Was sagt mir das?
    Dass meine Klasse gerade so einfach ist, dass die defaults das auch können? Oder ist mein Compiler grad so schlau? Ist es sicherer, wenn man die mitangibt, wirds dann Compilerunabhängiger?

    Hmm.. irgendwie hab ich im Gedächtnis, dass das gestern ohne die Dinger nicht funktioniert hat. Was hat sich verändert, ich weiß es nicht ..... lol ...

    #include <iostream>
    #include <vector>
    
    class A {
    public:
        A(const int dx, const int dy) : x(dx), y(dy) {}
     //   A(const A& einA) :x(einA.x), y(einA.y) {}
     //   A &operator=(A einA)  {swapi(einA); return *this;}
        void swapi(A& einA);
        int getX() const {return x;}
        int getY() const {return y;}
    
    private:
        int x;
        int y;
    };
    
    void A::swapi(A& einA) {
        std::swap(x,einA.x);
        std::swap(y,einA.y);
    }
    
    void wechseln(std::vector<A>& foo) {
        A foo2(3,4);
        foo[0]=foo2;
    }
    
    void wechseln2(std::vector<A*> boo) {
        A foo2(7,8);
        *(boo[0])=foo2;
    }
    
    int main () {
        std::vector<A> foo;
        foo.push_back(A(1,2));
        std::cout << foo[0].getX() << " " << foo[0].getY() << "\n";
        A foo3(4,5);
        foo[0]=foo3;
        std::cout << foo[0].getX() << " " << foo[0].getY() << " <- geht! \n";
        wechseln(foo);
        std::cout << foo[0].getX() << " " << foo[0].getY() << " <- geht! \n";
    
        std::vector<A*> boo;
        boo.push_back(&foo[0]);
        std::cout << boo[0]->getX() << " " << boo[0]->getY() << "  \n";
        wechseln2(boo);
        std::cout << boo[0]->getX() << " " << boo[0]->getY() << " <- geht! voll cool!  \n";
    
        return 0;
    }
    

    Ich hatte hier mal eine Frage zu einer Klasse gestellt, wo die Klasse ihre eigenen Elemente mitgezählt hat. Da war dann in der {} Klammer noch eine Anweisung. Hängt das mit den {} Klammern zusammen? Dass die Dreier/Fünferregel gerade in den Fällen notwendig ist, wo zusätzlich zur Initialisierung noch was getan werden muss?


  • Mod

    Ist es sicherer, wenn man die mitangibt, wirds dann Compilerunabhängiger?

    Das sicher nicht.

    Dass meine Klasse gerade so einfach ist, dass die defaults das auch können?

    Richtig - die implizit definierten Versionen tun das gleiche.



  • Lymogry schrieb:

    ähm jungs..... 😃 ich hab mal den Test gemacht und den Zuweisungs- und Kopierkonstruktor auskommentiert: es funktioniert immernoch 😮

    Was sagt mir das?

    Dass meine Klasse gerade so einfach ist, dass die defaults das auch können? Oder ist mein Compiler grad so schlau? Ist es sicherer, wenn man die mitangibt, wirds dann Compilerunabhängiger?

    Jeder Compiler generiert Code, der genau das macht, was du geschrieben hast.

    Ich hatte hier mal eine Fragei zu einer Klasse gestellt, wo die Klasse ihre eigenen Elemente mitgezählt hat. Da war dann in der {} Klammer noch eine Anweisung. Hängt das mit den {} Klammern zusammen? Dass die Dreier/Fünferregel gerade in den Fällein notwendig ist, wo zusätzlich zur Initialisierung noch was getan werden muss?

    Wenn nachdem Kopieren etwas lgetan werden muss, muss das idR im Destruktor
    rückgängig gemacht werden.



  • Supi!

    Dank eurer Hilfe wird mir immer einiges klarer.
    DANKE EUCH ALLEN! 🙂



  • FRAAGEE! 🙂

    Wie ihr im aktuellen Code seht, gibt es einen Vektor

    std::vector<A*> boo
    

    wo die Adressen von irgendwelchen A Klassenelementen liegen.
    Diese Inhalte kann man jetzt einfach überschreiben: wechseln2

    Aber was passiert, wenn die Größe von diesem Vektor in einer Subroutine vergrößert werden muss?
    Sowas geht natürlich nicht:

    void tuwas(std::vector<A*> boo) {
        boo.push_back(A*(1,2));  // das geht so nicht! 
        A foo3(9,10);
        boo.push_back(&foo3);   // so geht das natürlich auch nicht! 
    }
    

    Das Problem ist, dass dort alles erstmal temporär ist. Kommt man an einen Platzhalter oder so dran? 🙂


  • Mod

    Nein. Um die Lebenszeit des Objektes dynamisch zu machen, nutzt man new .

    boo.push_back( new A(1,2) );
    

    Allerdings gibt es hier ein Problem: Wann wirst du den Speicher wieder freigeben?



  • damit sich auch was im vektor wirklich ändert musst du ihn per call-by-reference statt per call-by-value übergeben:

    void tuwas(std::vector<A*>& boo) {  // <- das & steht in dem kontext für eine referenz
        // ...
    }
    


  • Arcoth schrieb:

    Nein. Um die Lebenszeit des Objektes dynamisch zu machen, nutzt man new .

    boo.push_back( new A(1,2) );
    

    Allerdings gibt es hier ein Problem: Wann wirst du den Speicher wieder freigeben?

    gnah .. keine guten Nachrichten 🙂
    ich könnte das natürlich am Ende von main mit delete wieder freigeben ... Ich muss dann aber auch nur die letzten Elemente vom Vector freigeben. Der ganzen Vector darf ich ja nicht anfassen. Dann müsste ich mir zusätzlich merken, wieviele neuen Elemente ich mit new geholt habe ... das ist ja eklig ^^

    Noch weitere Vorschläge? 😃

    afasfasfa schrieb:

    damit sich auch was im vektor wirklich ändert musst du ihn per call-by-reference statt per call-by-value übergeben:

    void tuwas(std::vector<A*>& boo) {  // <- das & steht in dem kontext für eine referenz
        // ...
    }
    

    AAHHH ... wie banane .. 😃
    DANKE! 😃



  • Lymogry schrieb:

    Noch weitere Vorschläge? 😃

    eigenen allocator schreiben



  • adfasdfad schrieb:

    Lymogry schrieb:

    Noch weitere Vorschläge? 😃

    eigenen allocator schreiben

    Du meine Güte ... 😉

    Call by Reference hats schon getan ... darauf bin ich nicht mehr gekommen, nachdem ich Sternchen gesehen habe! 😃
    (Warst du das nicht gerade eben?)



  • Lymogry schrieb:

    Call by Reference hats schon getan ... darauf bin ich nicht mehr gekommen, nachdem ich Sternchen gesehen habe! 😃

    pass auf, gleich kommt sone und sagt dir dass das beides call-by-value ist!

    Lymogry schrieb:

    (Warst du das nicht gerade eben?)

    ja weil ich unkreativ bin bzgl. namenswahl.



  • asfasdfas schrieb:

    Lymogry schrieb:

    Call by Reference hats schon getan ... darauf bin ich nicht mehr gekommen, nachdem ich Sternchen gesehen habe! 😃

    pass auf, gleich kommt sone und sagt dir dass das beides call-by-value ist!

    hihihi 😃
    Wir warten! 🙂



  • Ok ... zu früh gefreut????

    void tuwas(std::vector<A*> &boo) {
        A foo3(9,10);
        boo.push_back(&foo3);   // Neues Element als Platzhalter.
        *(boo[boo.size()-1])=foo3;  // Inhalt kopieren. Geht nicht!
    }
    

    Und das in main:

    std::vector<A*> boo;
        boo.push_back(&foo[0]);
        std::cout << boo[0]->getX() << " " << boo[0]->getY() << "  \n";
        wechseln2(boo);
        std::cout << boo[0]->getX() << " " << boo[0]->getY() << " <- Inhalt kopieren geht!  \n";
        std::cout << "foo.size()=" << boo.size() << "\n";
        tuwas(boo);
        std::cout << "nach tuwas: boo.size()=" << boo.size() << " <- wird größer  \n";
        std::cout << boo[1]->getX() << "Inhalt stimmt nicht! \n" ;
    

    Edit: da stand vorhin eine falsche Funktion 😉



  • du übergibst einen pointer auf ein lokales objekt, das ist böse.
    wusste nicht, dass du sowas machen willst. mach einfach einen vector aus A statt aus zeigern auf A. ansonsten musst du wohl oder übel (eher übel) einen allocator machen oder einen vector aus smart_ptr<A> machen. willst du beides nicht da es dir keine vorteile bringt. tl;dr -> std::vector<A>

    "Inhalt stimmt nicht"

    ja, der zeiger zeigt auf eine stelle im speicher an der nichts ist. undefiniertes verhalten. böse.



  • du übergibst einen pointer auf ein lokales objekt, das ist böse.
    

    Ja, deswegen jammer ich schon auf der letzten Seite ... 😃
    Es geht ja um den temporären Speicher. Wenn ich den in main hole und dann in einer Subroutine den Inhalt wechsel, ists kein Problem. Aber ich kann in einer Subroutine nicht die Größe des Arrays ändern ...

    wusste nicht, dass du sowas machen willst. mach einfach einen vector aus A statt aus zeigern auf A.
    

    Nun, warum ich das unbedingt mit Pointern lösen will:
    Die Klasse A hat noch abgeleitete Klassen. Und alle Elemente von A und deren Ableitungen sollen in ein Array rein. Ich möchte mir aber merken, welcher Typ jetzt drin ist. Wenn ich nur das Array über A laufen lassen, ist zwar alles drin, aber der Ableitungstyp geht verloren ...



  • ja dann mach es mit smart pointer:

    #include <iostream>
    #include <vector>
    #include <memory>
    #include <string>
    #include <sstream>
    struct base
    {
    	int a;
    	explicit base(int a = 0)
    		: a(a)
    	{
    	}
    	virtual std::string str() const
    	{
    		std::ostringstream mybuf;
    		mybuf << "i'm a base(" << this->a << ")\n";
    		return mybuf.str();
    	}
    };
    struct derived : base
    {
    	int b;
    	explicit derived(int a = 0, int b = 0)
    		: base(a), b(b)
    	{
    	}
    	virtual std::string str() const
    	{
    		std::ostringstream mybuf;
    		mybuf << "i'm derived(" << this->a << ", " << this->b << ")\n";
    		return mybuf.str();
    	}
    };
    typedef std::vector<std::shared_ptr<base>> container_type;
    void add_base(container_type& vec)
    {
    	vec.push_back(typename container_type::value_type(new base(5)));
    }
    void add_derived(container_type& vec)
    {
    	vec.push_back(typename container_type::value_type(new derived(2, 7)));
    }
    int main()
    {
    	container_type myvec;
    	add_base(myvec);
    	add_derived(myvec);
    	std::cout << "there are " << myvec.size() << " entries, they're:\n";
    	std::cout << myvec[0]->str() << myvec[1]->str();
    }
    

    braucht c++11 support. std::shared_ptr weist relativ intuitives kopier-/zuweisungsverhalten auf. naja nicht ganz, wenn du ein shared_ptr einem anderen zuweist, dann zeigen beide auf das selbe objekt. das zweite objekt ist dann auch noch gültig wenn das erste zerstört wurde (so dass man den besitz über scopes hinweg weitergeben kann). dass ein objekt den fundamentalen regeln zum kopieren usw unterliegt ist anforderung dafür, dass es in stl-containern (z.b. vector) abgelegt werden kann. um das delete kümmert sich shared_ptr selbst.


Anmelden zum Antworten