Effective C++ Item 10 - Wo ist der Wald?



  • Hallo,
    ich habe heute mal wieder "Effective C++" gelesen und bin dabei über etwas gestolpert, dass mir in den vier-fünf Lesedurchgängen davor nie aufgefallen ist. Hoffe einer von euch kann mir das erklären:
    In Item 10 implementiert Scott Meyers einen "class-specific operator new" für seine Airplane-Klasse. Das Ganze ist ein Memory-Pool der über eine simple einfach verkettete Liste implemetiert ist.

    void * Airplane::operator new(size_t size)
    {
        if (size != sizeof(Airplane)) return ::operator new(size);
    
        Airplane *p = headOfFreeList;
    
        // if p is valid, just move the list head to the
        // next element in the free list
        if (p)
          headOfFreeList = p->next;
        else {
          // allocate a big block of memory 
    // 1
          Airplane *newBlock = 
            static_cast<Airplane*>(::operator new( BLOCK_SIZE * sizeof(Airplane)));
    
          // link the objects together; skip the zeroth
          // element, because we'll return that
    // 2
          for (int i = 1; i < BLOCK_SIZE-1; ++i)
            newBlock[i].next = &newBlock[i+1];
    
          // terminate the linked list with a null pointer
          newBlock[BLOCK_SIZE-1].next = 0;
    
          // set p to front of list, headOfFreeList to
          // chunk immediately following
          p = newBlock;
          headOfFreeList = &newBlock[1];          
        } 
        return p;
    }
    

    Der Code ist simple und eigentlich einfach zu verstehen.

    Nur warum ist er korrekt?

    Ich habe die zwei Stellen die mich irritieren mit 1 und 2 gekennzeichnet.

    // 1
          Airplane *newBlock = 
            static_cast<Airplane*>(::operator new( BLOCK_SIZE * sizeof(Airplane)));
    

    Hier wird ein passend alignierter Speicherblock für BLOCK_SIZE Airplane-Objekte alloziert. Wir haben nun also Speicher für BLOCK_SIZE Objekte. Es wurde bisher aber noch *kein* Konstruktor aufgerufen (siehe new-Operator vs. Funktion operator new).

    Jetzt kommt aber:

    // 2
          for (int i = 1; i < BLOCK_SIZE-1; i++)
            newBlock[i].next = &newBlock[i+1];
    

    Hier referenzieren wir ein Attribut eines Airplane-Objekts. Und das obwohl bisher überhaupt kein solches Objekt existiert. Es wurde ja niemals ein Ctor aufgerufen.

    Deshalb meine Frage: Ist der Code korrekt? Und wenn ja, warum?
    Wo ist mein Denkfehler?

    PS: Mir ist klar, dass der Code funktioniert. Die Klasse hat weder Basisklassen, virtuelle Methoden noch Member für die ein Ctor aufgerufen werden muss. Ein Ctor-Aufruf ist hier (aus sicht der Laufzeitumgebung) unnötig. Kein normaler Compiler würde für diese Klasse einen Default-Ctor generieren. Das ändert aber nichts an der Tatsache, dass imo aus Sicht des Standards ein Objekt erst existiert (und damit referenziert werden darf), nachdem sein Ctor abgelaufen ist.



  • Airplane hat doch nichts anderes als statische Elemente und zwei Zeiger.

    Der Zeiger headOfFreeList wird mit null initialisiert (static).

    Hat man am ende nicht einfach sowas wie ne Struktur

    struct t
    {
       AirplaneRep * rep;
       Airplane *next;
    }
    

    die man n mal hintereinander allokiert und mit .next als index zugreift ?
    Also als wäre es ne stinknormale stuktur ?

    Ich hab ka. wie sich das static auswirkt.
    Bis auf die zwei Zeiger gibt es nurnoch das Union in dem Obejkt.

    😕



  • Also mein Verständnis für C, PODs und fiese Typecasts sagt mir, dass der Code völlig in Ordnung ist. Aber

    HumeSikkins schrieb:

    Hier referenzieren wir ein Attribut eines Airplane-Objekts. Und das obwohl bisher überhaupt kein solches Objekt existiert. Es wurde ja niemals ein Ctor aufgerufen.

    lässt sich auch nicht völlig von der Hand weisen.

    In 8.3.5 [basic.life] stehen einige Einschränkungen, u.a. dass static_cast für Airplane* -> void* einsetzbar ist (ob auch für void* -> Airplane* konnte ich aber nicht herauslesen) und

    If the object will be or was of a non-POD class type, the program has
    undefined behavior if:
    — the pointer is used to access a non-static data member or call a non-static member function of the object

    Auf der anderen Seite steht aber in 8.3.2

    [Note: the lifetime of an array object or of an object of POD type (3.9) starts as soon as storage with proper
    size and alignment is obtained, and its lifetime ends when the storage which the array or object occupies is
    reused or released. 12.6.2 describes the lifetime of base and member subobjects. ]

    und hier haben wir ja eigentlich eine Art Array...

    Falls Airplane ein POD ist, dann gibt es aber mit Sicherheit keine Probleme, da die Lebenszeit bei PODs ja gleich nach der Speicherallozierung beginnt.



  • tag schrieb:

    Falls Airplane ein POD ist, dann gibt es aber mit Sicherheit keine Probleme, da die Lebenszeit bei PODs ja gleich nach der Speicherallozierung beginnt.

    Ok. Airplane aus dem Buch ist ein POD-Typ. Aber ist das beabsichtigt oder passt das nur gerade gut zum Beispiel?
    Mich wundert es, dass Scott Meyers nirgends auf diesen Spezialfall hinweist.

    Morgen gehe ich hin und füge Airplane noch einen std::string planeType hinzu und schwupps hat mein Code undefiniertes Verhalten (zumindest wenn ich die bisherigen Antworten richtig verstanden habe).

    Also so ganz klar ist mir das Ganze noch nicht. Ich glaub' ich werde um eine Standard-Lesen-Session nicht drum rum kommen 😞



  • HumeSikkins schrieb:

    Morgen gehe ich hin und füge Airplane noch einen std::string planeType hinzu und schwupps hat mein Code undefiniertes Verhalten (zumindest wenn ich die bisherigen Antworten richtig verstanden habe).

    Jo, der Standard sieht das, soweit ich sehe, genauso. Siehe 3.8p5:

    If the object will be [...] of non-POD class type, the program has undefined behavior if:

    - the pointer is used to access a non-static data member or call a non-static member function of the object
    [...]

    Warum gehst du eigentlich von vornherein davon aus, dass Meyers' Code korrekt ist?



  • Bashar schrieb:

    Warum gehst du eigentlich von vornherein davon aus, dass Meyers' Code korrekt ist?

    In diesem konkreten Fall, da der Code in dem nicht ganz unbekannten Buch "Effective C++ - Second Edition" erschienen ist, welches mittlerweile in der 15ten Auflage vorliegt und wahrscheinlich von jedem ernstzunehmenden C++ Programmierer mindestens einmal gelesen wurde.
    Paart man dies mit der Tatsache, dass man weder in der Errata-Liste noch in
    comp.lang.c++.moderated einen Hinweis darauf findet, dass hier ein Problem vorliegt, scheint mir meine Annahme durchaus passend zu sein 🙂

    Mein Denkfehler wurde mittlerweile ja auch aufgedeckt:

    Das ändert aber nichts an der Tatsache, dass imo aus Sicht des Standards ein Objekt erst existiert (und damit referenziert werden darf), nachdem sein Ctor abgelaufen ist.

    Ganz so sieht es der Standard ja nicht...



  • Die Lebenszeit eines Objektes mit nicht-trivialem Konstruktor fängt genau dann an, wenn der Konstruktoraufruf vollendet ist (3.8p1). Dass es Subobjekt eines Arrays ist, ändert daran überhaupt nichts -- die Lebenszeit des Arrays mag eher anfangen, die des Subobjekts nicht. Wenn also vor dem Konstruktoraufruf auf das Element next zugegriffen wird, ist das Verhalten nach 3.8p5 undefiniert.

    Was war denn dein Denkfehler, vielleicht kopiere ich den gerade?



  • Bashar schrieb:

    Die Lebenszeit eines Objektes mit nicht-trivialem Konstruktor fängt genau dann an, wenn der Konstruktoraufruf vollendet ist (3.8p1).[...]

    Ich hatte das mit dem "nicht-trivialen" Konstruktor völlig verdrängt. Airplane ist ein POD-Typ und hat damit keinen nicht-trivialen Ctor. Die von dir zitierte (und von mir angenommene) Regel gilt hier also nicht.
    Ich hatte aber nur "class Airplane" gelesen, von da an an ein Objekt mehr im OOP-Sinne gedacht (also nicht POD) und bin dann über den geposteten Code gestolpert.

    Was war denn dein Denkfehler, vielleicht kopiere ich den gerade?

    Mein Denkfehler war, dass ich nicht über POD vs. Non-POD reflektiert habe.



  • Ähm ja richtig, dass der Code so wie gezeigt formal korrekt ist, hatten wir denke ich schon geklärt. Das Problem ist nach wie vor, dass Meyers nicht darauf hinweist, dass das nicht mit non-POD (z.B. Airplane mit string-Member) funktioniert bzw. dann undefiniertes Verhalten hat.



  • Meyers zeigt dann doch gleich danach wie man es richtig macht - mit einem statuc Pool Objekt (wobei es IMHO mit einem globalen Pool der von sizeof(Airplane) (statt nur Airplane) abhängt viel besser wäre).


Anmelden zum Antworten