Frage bezüglich Standard und Objekt-Lebenszeit



  • Hallo

    //Header.hpp
    namespace 
    {
      class Type
      {
        private:
        public:
          Type();
          ~Type();
      };
      extern Type Obj;
    }
    //Wie Header.cpp aussieht könnt ihr euch vorstellen
    //Main.cpp
    #include "Header.hpp"
    //Hier könnten ein paar Objekte stehen
    int main()
    {
    //Hier auch
    }
    

    Wird hier garantiert, dass der Konstruktor von " Obj " als erster und der Destruktor als letzter aufgerufen werden (also vor allen anderen Konstruktoren und nach allen anderen Destruktoren)?

    MfG, EOutOfResources

    EDIT: Für die Frage "wieso": C-Library (one more time)


  • Mod

    Das sollte, nach 3.6.2,1 undefiniert sein, weil die Aufteilung auf mehrere Übersetzungseinheiten die Ordnung durch den namespace-Scope aufhebt.

    Und allgemein ist das natürlich schlechtes Design, wenn es bei dir auf die Initialisierungsreihenfolge von globalen Objekten ankommt, aber das ist dir sicherlich selber klar.



  • SeppJ schrieb:

    Und allgemein ist das natürlich schlechtes Design, wenn es bei dir auf die Initialisierungsreihenfolge von globalen Objekten ankommt, aber das ist dir sicherlich selber klar.

    Das kannst du mal OpenAL sagen...



  • Wenn du wirklich einen unnamed Namespace gemeint hast, dann wäre die Reihenfolge definiert, wenn da nicht das "extern" wäre.

    Mit "extern" hast du ein Problem, da [unnamed-namespace-in-main.cpp]::Obj nirgends definiert wird, bloss deklariert. [unnamed-namespace-in-header.cpp]::Obj wird zwar vermutlich definiert, nur das ist ein anderes Objekt, mit einem anderen Typ, und aus main.cpp nicht ansprechbar.

    Angenomman es war ein benannter Namespace gemeint, dann ist die Reihenfolge undefiniert.

    Einzige Lösung dir mir jetzt einfällt, ist, ein Singleton zu verwenden, ala so:

    //Header.hpp
    namespace Foo // muss wie schon gesagt nen Namen haben
    {
      class Type
      {
        private:
          Type();
          ~Type();
        public:
          friend Type& GetInstance()
          {
              static Type Obj;
              return Obj;
          }
      };
    }
    
    namespace // diesmal wirklich unnamed, damit's in jeder Translation-Unit eine eigene (unabhängige) Referenz "Obj" gibt
    {
        Foo::Type& Obj = Foo::Type::GetInstance();
    }
    
    // Rest wie in deinem Beispiel
    

    Bzw. es muss natürlich "Type" nicht gleich ein Singleton sein. Wichtig ist, dass jede TU selbst "Type::GetInstance()" aufruft um ein Objekt zu initialisieren das in dieser TU definiert wird (=die "Obj" Referenz im unnamed Namespace).

    Beim ersten Aufruf von "Type::GetInstance()" wird dann das "static local" Objekt initialisiert, und das passiert dann garantiertermassen vor der Initialisierung von Dingen die nach dem #include "header.hpp" definiert sind.



  • Jetzt bin ich verwirrt. Das extern brauch ich wegen der ODR (nicht?). Und ich dachte, der anonyme Namensbereich (bzw. sein Inhalt) sei nur für die Übersetzungseinheit ansprechbar, die ihn auch definiert. Deshalb habe ich die Klasse " Type " in den anonymen Namensbereich getan (dass der User sie nicht anfassen kann). Und was ich mit all dem bezwecken will ist, dass eine Funktion aufgerufen wird und ihre Rückgabe gespeichert wird.



  • Der Punkt ist, daß die Ziele des anonymen Namensraums und des extern einander widersprechen. Durch den anonymen Namensraum sind die Bezeichner "Type" und "Obj" nur in der aktuellen Übersetzungseinheit bekannt (bzw. werden in jeder Übersetzungseinheit eindeutig neu deklariert), extern sagt aus, daß die Variable an anderer Stelle definiert werden soll.
    (und solange die Klasse im Header ist, kann sie auch woanders eingebunden werden)

    Eventuell solltest du mal erklären, was genau du mit der gesamten Kontruktion eigentlich bewirken willst.



  • CStoll schrieb:

    Eventuell solltest du mal erklären, was genau du mit der gesamten Kontruktion eigentlich bewirken willst.

    " Type " ist eine Klasse die im Konstruktor eine Funktion aufruft und im Destruktor ebenfalls eine Funktion aufruft. Das Ergebnis der ersten Funktion wird gespeichert. So ungefähr sieht das dann aus:
    Type.hpp:

    //Jaja, Include-Guards. Hab ich im Original...
    namespace
    {
      class Type
      {
        private:
          void* Handle;
          Type(const Type&);
          Type& operator= (const Type&) const;
        public:
          Type();
          ~Type();
          void* Get();
      };
      extern Type Obj;
    }
    

    Type.cpp:

    #include "Type.hpp"
    Type::Type()
    {
      this->Handle = Func_Begin();
    }
    Type::~Type()
    {
      Func_End(this->Handle);
    }
    void* Type::Get()
    {
      return this->Handle;
    }
    Type Obj;
    

    Der Konstruktor und der Destruktor sind im Original noch ein wenig praller, damit das Ganze ausnahmesicher ist.
    Noch konkreter:
    Die Funktion " Func_Begin " gibt ein Handle auf ein Device zurück (nötig für alle anderen Dinge der Lib) und die Funktion " Func_End " zerstört das Handle wieder.

    EDIT: Was ich mir gedacht habe:
    Ich wusste, dass ich einen Funktionsaufruf zu Beginn benötigte -> Gedanke an Konstuktor -> Klasse, von der eine Instanz existiert -> Gleichzeitig auch Speicher des Handles



  • Können denn eigentlich mehrere Objekte deiner Klasse existieren? Wenn ja, hat dann jedes Objekt sein eigenes Handle?
    In dem Fall kannst du die Geschichte mit dem anonymen Namensraum und der (doch nicht so) globalen Variablen verzichten und dort wo benötigt ein Objekt anlegen. Andernfalls dürfte die Singleton-Konstruktion, die hustbär vorgeschlagen hat, die stabilere Lösung sein.



  • CStoll schrieb:

    Können denn eigentlich mehrere Objekte deiner Klasse existieren?

    Rein von der Logik her, nein. Aber ich hab es noch nicht verboten, da ich ja der Einzige bin, der Zugriff auf die Klasse hat.

    CStoll schrieb:

    In dem Fall kannst du die Geschichte mit dem anonymen Namensraum und der (doch nicht so) globalen Variablen verzichten und dort wo benötigt ein Objekt anlegen.

    Daran habe ich auch gedacht, aber ich weis nicht, wie es mit der Performance der Funktion aussieht (werde ich mal testen).

    CStoll schrieb:

    Andernfalls dürfte die Singleton-Konstruktion, die hustbär vorgeschlagen hat, die stabilere Lösung sein.

    Werde ich auch mal testen.



  • EOutOfResources schrieb:

    CStoll schrieb:

    Können denn eigentlich mehrere Objekte deiner Klasse existieren?

    Rein von der Logik her, nein. Aber ich hab es noch nicht verboten, da ich ja der Einzige bin, der Zugriff auf die Klasse hat.

    Solche internen Details packt man dan aber nicht in Header, die überall offen im Quelltext untergebracht werden. Wenn diese Klasse nur dazu da ist, dein globales Device-Handle zu verwalten, kannst du sie auch in der ÜE lokal definieren, die mit diesem Handle arbeiten will.



  • So. Habe es nun mit dem Singleton-Pattern gemacht und soweit funktioniert alles wunderbar. Danke 👍 .



  • Wenn du nur ne Funktion aufrufen willst, dann vielleicht einfach so:

    // header.hpp
    
    namespace Detail
    {
    int CallMeOnceBeforeDoingStuff();
    }
    
    namespace
    {
    int const g_initHelperDummy = CallMeOnceBeforeDoingStuff();
    }
    
    // ---------------------------------------------------------------
    
    // header.cpp
    
    namespace Detail
    {
    
    boost::once_flag g_onceFlag = BOOST_ONCE_INIT;
    
    void CallMeOnceBeforeDoingStuffImpl()
    {
    // do whatever needs to be done
    }
    
    int CallMeOnceBeforeDoingStuff()
    {
        boost::call_once(CallMeOnceBeforeDoingStuffImpl, g_onceFlag);
    }
    
    }
    


  • EOutOfResources schrieb:

    Jetzt bin ich verwirrt.

    Das glaube ich.

    EOutOfResources schrieb:

    Das extern brauch ich wegen der ODR (nicht?).

    In diesem Fall verwendest Du es, um eine Definition in eine Deklaration zu verwandeln. So oder so hat das Ding eine externe Bindung. Nur ist der Name unaussprechbar und ÜE-spezifisch wegen des anonymen Namensraums.

    EOutOfResources schrieb:

    Und ich dachte, der anonyme Namensbereich (bzw. sein Inhalt) sei nur für die Übersetzungseinheit ansprechbar, die ihn auch definiert. Deshalb habe ich die Klasse " Type " in den anonymen Namensbereich getan (dass der User sie nicht anfassen kann).

    Diese Logik kann ich nicht nachvollziehen. Ich schätze, dass Du da noch ein paar Sachen falsch verstehst.

    Wegen des anonymen Namensraums gibt es in jeder ÜE, die diese Include-Datei einbindet, einen eigenen Typ "Type". Und nur der "Type" innerhalb von Type.cpp wird als einziger "ausdefiniert" (also Konstruktor & co). Auch die reine Deklaration von Obj im anonymen Namensraum ist sinnfrei, da du es nicht in einer anderen ÜE definieren kannst.


Log in to reply