Verständnisproblem: Elementinitialisierer



  • Hallo zusammen,
    ich beschäftige mich gerade mit der "Initialiserung von Teilobjekten". Ich habe eben den Elementinitialisierer kennengelernt. Bis jetzt kannte ich diesen (Doppelpunkt) nur aus der Rubrik "Vererbung".

    Leider ist mir der Sinn hinter einer solchen Elementinitialisierung nicht ganz klar. Bis jetzt habe ich meinen Konstruktor immer folgendermaßen geschrieben:

    #include <iostream>
    using namespace std;
    
    class CTest //Beispielklasse
    {
    private:
    	int zahl;
    public:
    	CTest(int);
    	const int& getZahl() const {return zahl;};
    };
    
    CTest::CTest(int z = 0) //ein für mich gewöhnlicher Konstruktor
    {
     zahl = z;
    }
    
    int main()
    {
    	CTest test(100);
    	cout << test.getZahl();
    	return 0;
    }
    

    Nun habe ich gelernt, dass man den Konstruktor auch mit dem Elementinitialisierer schreiben kann, wobei das Gleiche dabei herauskommt:

    CTest::CTest(int z = 0) : zahl(z)
    {
    }
    

    Nun habe ich einen leeren Anweisungsblock, doch ich verstehe nicht so recht, wo der Vorteil liegt.

    Braucht man diesen Operator (außer bei der Vererbung) häufig? Wo liegen die Vorteile. Bei meinem Beispiel sehe ich keine.

    Vielen Dank
    lg, freakC++



  • heyho,

    Die Initialisierungsliste dient dem Initialisieren objektzugehöriger Attribute (nicht-statischer Attribute) der eigenen Klasse.

    Vorteile:

    1. Referencen und Konstanten können nur mittels Initialisierungsliste initialisiert werden.

    2. Den Konstruktor der Basisklasse kannst du nur mittels Initialisierungslite aufrufen.

    3. Initialisierungsliste hat Performmancevorteil, da die Initialisierungsliste , wie's der Name schon sagt, die Attribute initialisiert.
    Machst du es ohne Initialisierungsliste, so werden die Attribute zuerst definiert, und dann wird ihnen im Rumpf etwas zugewiesen... es sind also 2Schritte nötig... mit einer Initialisierungsliste nur 1Schritt.



  • Der Sinn der Initialisierungsliste ist es, im Konstruktor nicht den Standardkonstruktor der Member aufzurufen, sondern einen anderen zu wählen. Das hat den Vorteil, dass der Aufruf des Standardkonstruktors gespart werden kann und ist nötig, wenn bestimmte Member keinen Standardkonstruktor haben.

    Beispiel:

    struct foo
    {
        int& i;
        const int ci;
        string s;
    
        // falsch:
        foo(int& ref)
        // bevor die 1. Anweisung ausgeführt wird, werden alle Standardkonstruktoren aufgerufen
        // Fehler: int& hat keinen Standardkonstruktor
        {
            i = ref;   // illegal
            ci = 42;   // illegal --> ci hätte undefinierten Wert
            s = "Hallo"; // operator= wird aufgerufen --> man hätte sich den Standardkonstruktoraufruf sparen können
        }
    
        // besser:
        foo(int& ref) : i(ref) /* jetzt möglich */, ci(42) /* auch möglich */, s("Hallo") /* Konstruktor string(const char*) wird aufgerufen, Standardkonstruktor wird gespart */
        {
            // keine weiteren Anweisungen nötig
        }
    };
    

    In deinem Beispiel ist das egal, allerdings empfehle ich dir, dir die Initialisierungsliste grundsätzlich zugunsten der Konsistenz anzugewöhnen.



  • In deinem oberen Beispiel wird zahl halt doppelt gesetzt. Erst wird es standardmäßig mit 0 initialisiert, und dann wird es im Rumpf des Konstruktors auf z gesetzt. Das untere Beispiel initialisiert zahl direkt mit z. Wenn du dort statt einem einfachen int eine Klasse hast, kann da auch etwas ganz anderes rauskommen. Es wird dort standardmäßig der Konstruktor ohne Parameter aufgerufen um das Objekt zu initialisieren. Soll direkt ein Konstruktor mit Parameter aufgerufen werden, brauchst du den Elementinitialisierer.



  • Hi,
    das mit der Initialisierung der Referenzen und Konstanten wusste ich nicht. Dass man Konstanten erst später initialisieren kann, ist ja irre 😃

    @Tobiking2: Das leuchtet sein. Der Elementinitialisierer hat mich überzeugt.

    Vielen Dank
    lg, freakC++



  • Tobiking2 schrieb:

    In deinem oberen Beispiel wird zahl halt doppelt gesetzt. Erst wird es standardmäßig mit 0 initialisiert, und dann wird es im Rumpf des Konstruktors auf z gesetzt.

    Das wage ich zu bezweifeln.

    #include <iostream>
    using namespace std;
    
    class CTest
    {
    private:
        int zahl;
    public:
        CTest(int);
        const int& getZahl() const {return zahl;};
    };
    
    CTest::CTest(int z = 0)
    {
         cout << zahl << endl; // zufälliger Wert, da wird nichts auto. mit 0 initialisiert.
    }
    
    int main()
    {
        CTest test(100);
    
        getchar();
    
        return 0;
    }
    


  • freakC++ schrieb:

    Dass man Konstanten erst später initialisieren kann, ist ja irre.

    Hä, wie meinst du den das?

    Man kann nie etwas "später initialisieren" (initial = Anfang),
    man kann nur "später etwas anders zuweisen", was bei Konstanten bekanntlicherweise nicht geht.

    Initialisierung = Definition mit Defaultzuweisung.



  • Dweb schrieb:

    Das wage ich zu bezweifeln.

    Hmm, du hast Recht. Aber irgendwie trudelt mir diese Nullinitialisierung bei Membervariablen durch den Kopf. War das vielleicht der Fall wenn man das Objekt per new erzeugt? Oder bin ich da ganz nach Java abgedriftet?



  • Tobiking2 schrieb:

    Dweb schrieb:

    Das wage ich zu bezweifeln.

    Hmm, du hast Recht. Aber irgendwie trudelt mir diese Nullinitialisierung bei Membervariablen durch den Kopf.

    Globale Variablen werden automatisch mit 0 initialisiert, falls nichts angegeben wird, oder du hast es mit Java verwechselt 😉

    #include <iostream>
    using namespace std;
    
    int i;
    static int si;
    
    int main()
    {
        cout << i << endl << si;
    
        cin.sync();
        cin.get();
        return 0;
    }
    


  • Ok, das mit "später initialisieren war falsch". Sie werden ja direkt initialisiert. Ich kannte nur diese Methode nicht, doch sie gefällt mir immer mehr.

    Danke!
    lg, freakC++



  • freakC++ schrieb:

    Ok, das mit "später initialisieren war falsch". Sie werden ja direkt initialisiert. Ich kannte nur diese Methode nicht, doch sie gefällt mir immer mehr.

    Nur nochmal etwas Klarer:

    Die Initialisierung findet ausschließlich in der Initialisierungsliste statt, im Konstruktorrumpf werden nachträgliche Zuweisungen durchgeführt (dein Text deutet an, das du die Zuweisung im Konstruktor weiterhin als Initialisierung betrachtest - umgangssprachlich würde ich dem beipflichten, programmiertechnisch nicht).



  • Tobiking2 schrieb:

    Aber irgendwie trudelt mir diese Nullinitialisierung bei Membervariablen durch den Kopf. War das vielleicht der Fall wenn man das Objekt per new erzeugt? Oder bin ich da ganz nach Java abgedriftet?

    Bei POD-Objekten führt eine Klammer (Syntax eines Defaultkonstruktor-Aufrufs, es sind jedoch keine Konstruktoren vorhanden) zu einer Null-Initialisierung.

    // Konstruktor-Initialisierungsliste
    MyClass::MyClass() : myPOD() {}
    
    // temporäres Objekt
    POD myPOD = POD();
    
    // new-Ausdruck
    new POD();
    

    Nicht initialisiert wird das Objekt hingegen in solchen Situationen:

    MyClass::MyClass() {}
    
    POD myPOD;
    
    new POD;
    

Anmelden zum Antworten