class member initialization



  • Hallo zusammen,

    ich habe eine Klasse, die ua. ein array als private Attribut hat, welches ich im constructor zur Laufzeit dynamisch erstelle. Die Länge des Arrays wird in einer private const Variable gespeichert. Das ganze sieht also folgendermassen aus:

    class MyClass
    {
      private:
        int *array;
        const int SIZE;
    
      public:
        MyClass(int s = 10) : SIZE(s) { array = new int [SIZE]; }
    };
    

    So, das ganze funktioniert auch so wie ich es will, das const int SIZE Attribut wird im constructor durch die member initialization entsprechend gesetzt, falls der constructor ohne Parameter aufgerufen wird, dann wird auch richtig der default value von 10 genommen.

    ABER: was ist, wenn der Parameter < 1 ist? Dann würde die Erstellung des Arrays keinen Sinn machen. Also habe ich versucht, den constructor folgendermassen zu erweitern:

    MyClass(int s = 10) : SIZE(s)
    {
      if( s < 1 )
        s = 10;
    
      array = new int [SIZE];
    }
    

    Dies führt aber nicht zum gewünschten Ergebnis, da das attribut SIZE anscheinend bereits vor der if-Abfrage gesetzt ist. Das heisst, ich ändere damit zwar im Falle von s < 1 auf s = 10, aber das bringt mir nichts, weil das SIZE dadurch nicht mehr geändert wird.

    Meine Frage also: Kann ich das irgendwie im constructor abfragen und ggf. ändern, BEVOR dss Attribut SIZE gesetzt wird?

    Gruss und vielen Dank im Voraus!
    Matthias



  • Hi,

    die Größe eines Arrays gibt man als std::size_t an. Dieser Typ ist sowieso unsigned.

    Möglich wäre aber folgendes:

    : SIZE(s > 0 ? s : 10)
    

    Besser ist aber, im Fall <= 0 einen Fehler zu signalisieren (eine Exception zu werfen).



  • Vielen Dank, genau das habe ich gesucht.
    Schönen Tag noch!



  • Du möchtest oder musst vermutlich "Data-Encapsulation" zu betreiben! (Lob)

    Dann musst du alle Attribute per private schützen und nur über entsprechende public-Methoden den
    Zugriff gewähren.

    Wenn alle Instanzen deiner Klasse einen SIZE=10 besitzen sollen UND sich dieser nicht mehr ändern soll,
    würde ich das Attribut so setzen:

    static const int SIZE=10;

    die Sache static const Variablen inititieren war glaub ich etwas tricky... macht man die in der Klasse oder ausserhalb der Klasse?



  • template<typename T>
    class container
    {
        T* m_array;
        const std::size_t m_size;
    
    public:
        explizit container(std::size_t size) 
            : m_size(size), m_array(new T[size])
        {}
        ~container() { delete [] m_array; }
    };
    

    wobei dann die Frage ist, wofür du überhaupt noch dynamische Arrays nutzt?



  • gorgoyle schrieb:

    die Sache static const Variablen inititieren war glaub ich etwas tricky... macht man die in der Klasse oder ausserhalb der Klasse?

    Mit ganzzahligen Konstanten kannst Du es in der Klasse machen – falls Dein Compiler modern genug ist. Für andere Werte verlagert man die Definition (NICHT aber die Deklaration) nach außerhalb der Klasse.

    struct Test {
        // Deklaration.
        static std::string const cstr;
    };
    
    // Definition. KEIN 'static'-Schlüsselwort:
    std::string const Test::cstr = "Hallo";
    


  • Für andere Werte verlagert man die Definition (NICHT aber die Deklaration) nach außerhalb der Klasse.

    Das ist falsch. Auch statische, konstante, integrale Objekte einer Klasse müssen definiert werden. Nur die Initialisierung kann sich in dem Fall in der Klasse befinden, um das Objekt als constant expression nutzen zu können.

    If a static data member is of const integral or const enumeration type, its declaration in the class
    definition can specify a constant-initializer which shall be an integral constant expression (5.19). In that
    case, the member can appear in integral constant expressions. The member shall still be defined in a name-
    space scope if it is used in the program and the namespace scope definition shall not contain an initializer.



  • Freundlich schrieb:

    Für andere Werte verlagert man die Definition (NICHT aber die Deklaration) nach außerhalb der Klasse.

    Das ist falsch. Auch statische, konstante, integrale Objekte einer Klasse müssen definiert werden. Nur die Initialisierung kann sich in dem Fall in der Klasse befinden, um das Objekt als constant expression nutzen zu können.

    Hmm, Mist. Mein Compiler braucht die Extra-Definition nicht. Wie soll das denn in Code aussehen? So?

    struct test {
        static const int i = 1;
    };
    
    const int test::i;
    

    (Funktioniert. Wunderbar. Aber die letzte Zeile ist im aktuellen GCC und VC++ überflüssig.)



  • (D)Evil schrieb:

    template<typename T>
    class container
    {
        T* m_array;
        const std::size_t m_size;
    
    public:
        explizit container(std::size_t size) 
            : m_size(size), m_array(new T[size])
        {}
        ~container() { delete [] m_array; }
    };
    

    Ohoh, das ist nicht gut. Warum? naja, es könnte jemand auf die Idee kommen, in der Initialisierungsliste an zweiter Stelle

    m_array(new T[size])
    

    zu schreiben. Na und? And we enter the dark, dusty realms of Undefined Behaviour...
    Folgendes passiert: Der Compiler initialisiert Klassenmember in der Reihenfolge ihrer Dekaration, und nicht in der Reihenfolge der Initialisierungsliste. Soll heißen, hier würde das m_array(new T[m_size]) vor dem m_size(size) ausgeführt, obwohl es dahinter steht. Und da m_size also noch nicht initialisiert ist, ist die Größe des Arrays undefiniert. Von daher sollte man immer die Initiailisierungsliste in gleicher Reihenfolge wie die Deklarationen schreiben, um so subtile Fehler zu vermeiden und sich stundenlanges Bugtracking zu sparen.


  • Mod

    Konrad Rudolph schrieb:

    Freundlich schrieb:

    Für andere Werte verlagert man die Definition (NICHT aber die Deklaration) nach außerhalb der Klasse.

    Das ist falsch. Auch statische, konstante, integrale Objekte einer Klasse müssen definiert werden. Nur die Initialisierung kann sich in dem Fall in der Klasse befinden, um das Objekt als constant expression nutzen zu können.

    Hmm, Mist. Mein Compiler braucht die Extra-Definition nicht. Wie soll das denn in Code aussehen? So?

    struct test {
        static const int i = 1;
    };
    
    const int test::i;
    

    (Funktioniert. Wunderbar. Aber die letzte Zeile ist im aktuellen GCC und VC++ überflüssig.)

    Compiler sind bei solchen Dingen häufig nachsichtig, und Verletzungen der ODR müssen im Allgemeinen nicht diagnostiziert werden. Eine fehlende Definition wird für gewöhnlich frühestens beim Linken erkannt - aber nur dann, wenn tatsächlich eine Referenz auf das jeweilige Symbol existiert, nicht schon dann, wenn eine solche Referenz bloß existieren könnte. Dem Compiler steht es frei, solche Referenzen zu eliminieren, indem z.B. Ausdrücke bereits beim Compilieren ausgewertet werden, in solchen Fällen wird das Auslassen der Definition dann nicht diagnostiziert werden. Umgekehrt ist der Compiler aber nur dort verpflichtet, Ausdrücke beim Compilieren auszuwerten, wenn die Sprache an dieser Stelle einen konstanten Ausdruck erwartet. In allen anderen Fällen ist der Compiler völlig frei - und ein Programm ist nur dann konform, wenn es mit jeder in diesen Grenzen denkbaren Auswertungsentscheidung des Compilers funktioniert. Mithin ergibt sich Folgendes:
    1. Eine integrale statische Memberkonstante einer Klasse kann bereits bei ihrer Deklaration initialisiert werden (es ist dann trotzdem nur eine Deklaration).
    2. Jede folgende Verwendung des Wertes dieser Konstanten ist ein konstanter Integraler Ausdruck.
    3. Eine Definition der Konstanten ist nur dann nicht erforderlich, wenn der Wert in einem Ausdruck verwendet wird, und an der Stelle ein konstanter Ausdruck stehen muss.
    Zum Beispiel:

    struct Foo
    {
        static const int bar = 42;
        char x[bar]; // erfordert keine Definition
        void f(int y = bar); // erfordert Definition (wenn irgendwo mit Defaultargument aufgerufen)
        void g()
        {
            char* p = new char[bar]; // erfordert Definition
            char* q = new (char[bar]); // erfordert keine Definition
            char (*r)[bar] = new char[10][bar]; // erfordert keine Definition
            bar; // erfordert Definition
            &bar; // erfordert Definition
        }
    };
    


  • @pumuckl: Hmm naja ok wenn ich m_array(new T[m_size]) da stehen hätte, hättest du recht 😛



  • camper schrieb:

    Umgekehrt ist der Compiler aber nur dort verpflichtet, Ausdrücke beim Compilieren auszuwerten, wenn die Sprache an dieser Stelle einen konstanten Ausdruck erwartet.

    Danke für die Erklärung.

    Ich habe leider gerade mein Effective C++ nicht zur Hand, erinnere mich aber vage, dass es Meyers auf gleiche Weise falsch gemacht hat wie ich. Daher verzeihe ich mir selbst mal. 😉


Anmelden zum Antworten