Klassen-Paradoxon



  • schönen guten Tag alle zusammen.
    ich hänge schon seit einer Weile an einem aus meiner Sicht eigenartigen Problem rum.
    habe dieses auf das Nötigste reduzierz und unten gepostet.

    kurze Erläuterung:
    habe zwei Klassen geschrieben (Klasse_Eins und Klassse_Zwei).
    Klasse_Zwei enthält nur zwei Objekte von Klasse_Eins(wie man unten alles sieht).

    mir ist unklar, warum das objekt x die gleichen Werte in a hat wie y.
    dabei wird für x niemals der 2.Konstruktor der Klasse_Zwei aufgerufen.

    übrigens: wenn ich in "test3.cpp" die zeilen 31 und 32 vertausche passiert das nicht.

    ganz unten poste ich mal die Ausgabe der main.

    ich selber bin Student. kenne mich also in der Matarie etwas aus. doch blicke hier nicht durch. wenn einer eine schlau Erklärung hat, wäre ich echt dankbar.

    Datei "test.cpp"

    #include <iostream>
    #include "test3.cpp"
    
    using namespace std;
    
    int main()
    {
        Klasse_Zwei obj(8);
    
        for(int i=0;i<5;i++)
                cout<<obj.x.a[i]<<endl;
    
        cout<<endl;
    
        for(int i=0;i<5;i++)
                cout<<obj.y.a[i]<<endl;
    
        cin.get();
    }
    

    Datei "test3.cpp"

    #ifndef _TEST_H
    #define _TEST_H
    
    #include <iostream>
    
    using namespace std;
    
    class Klasse_Eins
    {
          private:
          public:
                 int a[5];
    
                 Klasse_Eins(){cout<<"hier wird 1.Konstruktor aufgerufen!"<<endl;};
                 Klasse_Eins(int wert)
                 {
                                 cout<<"hier wird 2.Konstruktor aufgerufen!"<<endl;
                                 for(int i=0;i<5;i++)a[i]=i*wert;
                 }
    };
    
    class Klasse_Zwei
    {
          private:
          public:
                 Klasse_Eins x,y;
    
                 //Konstruktor
                 Klasse_Zwei(int wert)
                 {
                                 y=Klasse_Eins(wert);
                                 x=Klasse_Eins();
                 }
    
    };
    #endif
    

    Ausgabe der main()
    **
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 2.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    0
    8
    16
    24
    32

    0
    8
    16
    24
    32
    **



  • Klasse_Zwei(int wert)
     : y(wert)
    {
    }
    

    Warum? Member werden vor dem Eintritt in den Konstruktorbody initialisiert, damit du dort auch darauf zugreifen kannst. Um diese Initialisierung selber in die Hand zu nehemen, (also die Default-Initialisierung zu vermeiden) gibt es die Initialisierungsliste.

    BTW: .cpp-Dateien sind Implementierungsdateien. Diese werden kompiliert und nicht includiert. Wenn du Namen in einer anderen Übersetzungseinheit bekannt geben willst, nutze Header.



  • Die Aussage von l'abra d'or ist zwar korrekt, beantwortet aber wohl deine Frage nicht...

    Ich fand deine Behauptung erstmal etwas komisch und hab es bei mir durch
    den Compiler gejagt:

    Ausgabe:

    0
    0
    -1073743488
    -1073743388
    0
    
    0
    8
    16
    24
    32
    

    Jetzt zur Erklärung, warum das bei dir (wahrscheinlich) so ist...

    Ein Array als member besitzt ja wie du sicherlich weißt die Werte, die
    gerade (zufällig) im Speicher sind.

    In Zeile 31 legst du ein Objekt auf dem Stack an mit 0 8 16 24 32
    Der Inhalt dieses Objektes wird dann durch den operator= in dein Member y
    kopiert.
    In Zeile 32 legst du ein weiteres Objekt auf dem Stack, das (soweit meine Vermutung) genau die gleiche Adresse hat wie das zuvor. Dort steht also
    noch der Inhalt 0 8 16 24 32 in dem Speicherbereich für a drin.
    Der Inhalt dieses Objektes wird dann durch den operator= in dein Member x
    kopiert.

    Tada... Beide haben die gleichen Werte 😉

    Aber generell gebe ich l'abra d'or recht: Initialisierungsliste ist hier mehr als sinnvoll....

    Gruß,
    CSpille



  • Ah, wunderbar, jetzt ist mir auch das Problem klar 😃 Hab mir den Post durchgelesen und bei der Länge einfach die Frage nicht gefunden.
    Ich dachte es geht ihm darum, dass da erst "Konstruktor 1" steht, obwohl er "Konstruktor 2" aufruft.



  • l'abra d'or schrieb:

    Ah, wunderbar, jetzt ist mir auch das Problem klar 😃 Hab mir den Post durchgelesen und bei der Länge einfach die Frage nicht gefunden.
    Ich dachte es geht ihm darum, dass da erst "Konstruktor 1" steht, obwohl er "Konstruktor 2" aufruft.

    Hab ich aber auch erst gedacht und wollte das Posting schon als beantwortet
    einordnen 😃



  • Ja, vielen Dank erst mal.
    das mit der Initialisierungsliste hat echt mein Problem gelöst.

    In Zeile 32 legst du ein weiteres Objekt auf dem Stack, das (soweit meine Vermutung) genau die gleiche Adresse hat wie das zuvor.

    meine Frage kann ich denke ich mal erwas vertiefen:
    warum hat das neue Objekt denn die gleiche Adresse?

    genau genommen kann dies doch gar nicht sein.
    ich habe mal dieses test-program etwas erweitert und in der main noch die Adresse der Objekte ausgeben lassen.

    wie ihr sehen könnt sind die Adressen gar nicht gleich (ganz unten die Ausgabe).

    was wir jedoch beobachten können ist, dass die Adressen der Objekte zwar verschieden sind doch sie gleich initialisiert werden.
    und das interessante ist, dass es für alle fünf Objekte der Fall ist.

    also wie gesagt, mein Problem konnte ich umgehen.
    doch was da passiert kann ich mir nicht ganz erklären.

    Datei "test.cpp":

    #include <iostream>
    #include "test3.cpp"
    
    using namespace std;
    
    int main()
    {
        Klasse_Zwei obj(8);
    
        cout<<&obj.x<<endl;
    
        for(int i=0;i<5;i++)
                cout<<obj.x.a[i]<<endl;
        cout<<endl;
    
        cout<<&obj.y<<endl;
        for(int i=0;i<5;i++)
                cout<<obj.y.a[i]<<endl;
        cout<<endl;
    
        cout<<&obj.z<<endl;
        for(int i=0;i<5;i++)
                cout<<obj.z.a[i]<<endl;
        cout<<endl;
    
        cout<<&obj.v<<endl;
        for(int i=0;i<5;i++)
                cout<<obj.v.a[i]<<endl;
        cout<<endl;
    
        cout<<&obj.w<<endl;
        for(int i=0;i<5;i++)
                cout<<obj.w.a[i]<<endl;
    
        cin.get();
    }
    

    Datei "test3.cpp":

    #ifndef _TEST_H
    #define _TEST_H
    
    #include <iostream>
    
    using namespace std;
    
    class Klasse_Eins
    {
          private:
          public:
                 int a[5];
    
                 Klasse_Eins(){cout<<"hier wird 1.Konstruktor aufgerufen!"<<endl;};
                 Klasse_Eins(int wert)
                 {
                                 cout<<"hier wird 2.Konstruktor aufgerufen!"<<endl;
                                 for(int i=0;i<5;i++)a[i]=i*wert;
                 }
    };
    
    class Klasse_Zwei
    {
          private:
          public:
                 Klasse_Eins x,y,z,v,w;
                 Klasse_Zwei(int wert)
                 {
                                 y=Klasse_Eins(wert);
                                 x=Klasse_Eins();
                                 z=Klasse_Eins();
                                 v=Klasse_Eins();
                                 w=Klasse_Eins();
                 }
    
    };
    #endif
    

    Ausgabe der main()
    **
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 2.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    hier wird 1.Konstruktor aufgerufen!
    0x22ff00
    0
    8
    16
    24
    32

    0x22ff14
    0
    8
    16
    24
    32

    0x22ff28
    0
    8
    16
    24
    32

    0x22ff3c
    0
    8
    16
    24
    32

    0x22ff50
    0
    8
    16
    24
    32
    **



  • @astro108, es geht um die Adressen der temporären, namenlosen Objekte in Klasse_Zwei(int).

    Um die zu sehen müsstest du "this" in Klasse_Eins() und Klasse_Eins(int) ausgeben lassen.



  • Die Sache ist anders gemeint. Nicht x,y haben die Selbe Adresse, sondern die temporären Objekte die du erzeugst:

    Klasse_Zwei(int wert)
                 {
                                 y=Klasse_Eins(wert);  // 1)
                                 x=Klasse_Eins();      // 2)
                                 z=Klasse_Eins();
                                 v=Klasse_Eins();
                                 w=Klasse_Eins();
                 }
    

    Bei 1) erzeugt Klasse_Eins(wert) ein temporäres Objekt. x,y,z,v,w sind schon initialisiert. Das temporäre Objekt wird y zugewiesen und danach wieder zerstört. Der Stack hat jetzt aber evtl. schon Werte rumliegen. Jetzt erzeugst du in 2) (und den folgenden Zeilen) wieder temporäre Objekte, die genau da liegen, wo das temp. Objekt aus 1) gelegen hat. Da das Member-Array nicht explizit initialisiert wird, kann es durchaus sein, dass es die Werte, die schon im Speicher liegen, direkt annimmt. Das ist aber reiner Zufall wenn es funktioniert und sollte nicht verwendet werden. Immer alles, worauf du dich verlassen willst, selber initialisieren.



  • @hustbaer, ja in der tat. hab das ausgeben lassen, was du gesagt hast und die adressen auf dem Stack sind die gleichen.

    bin auch einverstanden, dass es eher Zufall ist. Doch eigenartig, dass dieser Zufall so ausnahmslos auftritt. ich dem Projekt, an dem ich gerade dransitze hatte ich dieses "Phänomen" mit durchaus komplexeren Klassen als den trivialen hier.

    aber gut ich denke mal, dass der reservierte Stack einfach nicht überschrieben wurde.

    auf jeden fall danke ich euch und betrachte das Problem für mich als erledigt 🙂



  • astro108 schrieb:

    bin auch einverstanden, dass es eher Zufall ist. Doch eigenartig, dass dieser Zufall so ausnahmslos auftritt. ich dem Projekt, an dem ich gerade dransitze hatte ich dieses "Phänomen" mit durchaus komplexeren Klassen als den trivialen hier.

    Naja...

    Ist ja nicht wirklich Zufall. Dein Compiler nimmt sich halt immer die nächste
    verfügbare Stackadresse. Da das Objekt davor nicht mehr erreichtbar ist, kann
    er diese halt wieder verwenden. Alles andere wäre Verschwendung.
    Mein Compiler optimert evtl. etwas, so dass er gleich das Objekt gleich auf dem
    Heap verwendet. (Ist aber nur ne Vermutung mit der Optimierung)


Log in to reply