Funktion nur einmal aufrufen



  • Hallo,
    ich habe eine Klasse geschreiben (von der es auch mehr als nur ein Objekt geben kann / keine Singleton-Klasse). Damit diese Klasse richtig funktioniert muss einmal am Anfang vom Programm eine Funktion aufgerufen werden (aber nur EINMAL im Programm, nicht einmal pro Objekt). Muss ich das nun wirklich an den Anfang der main-function schreiben (gefällt mir überhaupt nicht, weil die Funktion ja mit der Klasse in Verbindung stehen sollte)??



  • Muss es wirklich am Anfang des Programms sein, oder reicht auch unmittelbar vor der ersten Benutzung der Klasse? Falls letzteres der Fall ist, lass die Funktion einen Wert (z.B. ein bool) zurückliefern, definiere sie als statisches member deiner Klasse und spendiere der Klasse ein statisches Member vom entsprechenden Typ, das du mit der Funktion initialisierst:

    //X.hpp:
    class X
    {
      static bool myFunnyInitFunction()
      {
        /* .. */
      }
      static bool b;
    };
    
    //X.cpp:
    bool X::b = myFunnyInitFunction();
    


  • Zum Beispiel so:

    struct MyClass // deine klasse
    {
        MyClass()
        {
            static bool firstCall = true; // lebt vom ersten aufruf bis zum ende des programms
            if(firstCall)
            {
                initFunction();       // die funktion die nur einmal aufgerufen werden soll
                firstCall = false;
            }
        }
    };
    

    So wird die gewünschte Funktion genau einmal aufgerufen, und zwar dann wenn das erste Objekt von deiner Klasse erzeugt wird.
    Aber Achtung, ist nicht threadsafe.



  • Folgendes wäre eine Lösung die dich vielleicht interessiert:

    //foo.h
    
    #ifndef FOO_H
    #define FOO_H
    
    #include <iostream>
    
    class foo
    {
    public:
    
    	foo()
    	{
    		std::cout << "foo\n";
    	}
    
    private:
    
    	static void foo_init()
    	{
    		std::cout << "foo_init\n";
    	}
    
    	static const struct run_foo_init{ run_foo_init(){ foo::foo_init(); } } rfi; //[EDIT]das ganze noch etwas kürzer und const
    };
    
    #endif //FOO_H
    
    //foo_once.h
    
    #ifndef FOO_ONCE_H
    #define FOO_ONCE_H
    
    #include "foo.h"
    
    const foo::run_foo_init foo::rfi;
    
    #endif //FOO_ONCE_H
    
    //main.cpp
    #include "foo_once.h"
    
    int main(int argc, char *argv[])
    {
    	foo f;
    }
    
    //Ausgabe:
    foo_init
    foo
    

    In deiner main.cpp includest du foo_once.h und in allen anderen Dateien in denen du foo benützen willst foo.h.

    Das sollte auch Threadsicher sein solange du den selben "Trick" nicht benützt um einen Thread zu erstellen 😛



  • rean schrieb:

    [...]Das sollte auch Threadsicher sein[...]

    Ne, das ist auch nicht threadsicher.



  • Tachyon schrieb:

    rean schrieb:

    [...]Das sollte auch Threadsicher sein[...]

    Ne, das ist auch nicht threadsicher.

    Hm, ok.

    Afair wird statischer Speicher doch garantiert vor main initialisiert?

    Wenn man seine Threads erst in main erstellt und startet sollte die Funktion also garantiert vorher ausgeführt worden sein oder verstehe ich da was grundlegendes falsch?



  • rean schrieb:

    ...
    Afair wird statischer Speicher doch garantiert vor main initialisiert?...

    Ich glaube, dass hier der Fehler liegt: IIRC, sichert der Standard nur zu, dass statics rechtzeitig vor der ersten Nutzung initialisiert sind.

    Andererseits könntest Du natürlich einen ersten Zugriff machen, bevor der erste Thread erstellt wird ... allerdings läufst Du immer noch Gefahr, dass der Compiler diesen wegoptimiert.

    Gruß,

    Simon2.



  • Ich bin mir gar nicht mehr so sicher. Vielleicht ist das doch gar nicht so schlecht. 😃



  • Jetzt bräuchten wir die kompetente Meinung eines Standard-Spezialisten 😛
    Hab schon ein wenig gestöbert aber konnte nix Definitives finden.



  • rean schrieb:

    Jetzt bräuchten wir die kompetente Meinung eines Standard-Spezialisten 😛
    Hab schon ein wenig gestöbert aber konnte nix Definitives finden.

    Naja, der Standard sagt, dass (Klassen)globale static Variablen beim Programmstart initialisiert werden. Ich überlege allerdings, ob es Probleme mit Race Conditions geben kann, wenn in einer anderen Übersetzungseinheit foo zur Initialisierung einer anderen globelen Variable herangezogen wird.



  • Ausserdem sagt der Standard nichts über Threading aus. Das wird im Standard fast komplett ausgeblendet.

    Simon


  • Administrator

    @Tachyon,
    Nein, der Standard ist etwas komplizierter bei non-local objects. Die Objekte werden spätestens dann initialisiert, wenn die erste Funktion aus der entsprechenden TU aufgerufen wird, vorher werden sie zero-initialised. Das macht ja auch static public Membervariablen von Klassen so gefährlich.

    Bei der Lösung von pumuckl heisst dies, dass spätestens bei der ersten Verwendung der Klasse, also erstellen eines Objektes oder aufrufen einer Funktion, die init-Funktion aufgerufen wird.

    Bei der Lösung von Registrierter Troll, werden keine non-local Objekte benötigt, dadurch besteht das Problem nicht. Dafür ist seine Lösung nicht Threadsicher.

    Bei der Lösung von rean, wird die init-Funktion garantiert gleich beim Programmstart aufgerufen, weil der zero-initiliser vom struct der Defaultkonstruktor ist.

    Da beim Aufstart eines Programmes immer nur ein Thread ist, ist die Lösung von rean, meiner Meinung nach, auch Thread-sicher.

    Bei Der Lösung von pumuckl bin ich mir nicht ganz sicher. Aber eine Initialisierung einer Variable dürfte eigentlich nur einmal passieren. Da der Standard aber nichts über Threads sagt, bin ich mir da schlicht und einfach nicht sicher.

    Grüssli



  • Dravere schrieb:

    Die Objekte werden spätestens dann initialisiert, wenn die erste Funktion aus der entsprechenden TU aufgerufen wird, vorher werden sie zero-initialised.

    Das möchte ich doch stark bezweifeln. Was du da sagst bedeutet im Grunde, dass für diese Objekte erst ein (eventuell nichtmal vorhandener) Default-Ctor aufgerufen würde, später gefolgt von einem zweiten Ctor Aufruf für das selbe Objekt, was völlig wider die allgemeine Auffassung von Objektlebenszeiten in C++ geht. Wahrscheinlicher ist, dass für die entsprechenden Objekte im Datensegment Speicher bereitgehalten wird und sie dann zum gegebenen Zeitpunkt an der entsprechenden Stelle konstruiert werden.


  • Administrator

    Also ich habe das so verstanden, ich zitiere mal den Ausschnitt aus dem Standard.

    C++ 2003, 3.6.2 Initialization of non-local objects, Abschnitt 1 schrieb:

    Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization
    takes place.
    Zero-initialization and initialization with a constant expression are collectively called static
    initialization; all other initialization is dynamic initialization. Objects of POD types (3.9) with static stor-
    age duration initialized with constant expressions (5.19) shall be initialized before any dynamic initial-
    ization takes place. Objects with static storage duration defined in namespace scope in the same translation
    unit and dynamically initialized shall be initialized in the order in which their definition appears in the
    translation unit. [Note: 8.5.1 describes the order in which aggregate members are initialized. The initial-
    ization of local static objects is described in 6.7. ]

    Grüssli



  • Das nenn ich mal missglückte Formulierung im Standard. Für mich liest sich das wie folgt:

    Statische Objekte werden genau einmal initialisiert, und zwar in der folgenden Reihenfolge

    1. zero-initialisierte Objekte (das ist nicht default-initialisiert, siehe 8.5)
    2. PODs, die mit einem konstanten Ausdruck initialisiert werden
    3. dynamisch initialisierte Objekte, d.h. Objekte, die mit Funktionsergebnissen, nichtkonstanten anderen statischen Variablen oder nichttrivialen Konstruktoren initialisiert werden. letzteres schließt Klassen ein, die zwar einen trivialen (compilergenerierten) default-Ctor haben, die aber Member oder Basisklassen mit nichttrivialen Konstruktoren haben.


  • @pumuckl:
    Genau.
    Wirklich gut verlassen kann man sich nur auf 1) und 2), also zero-init und const-init.

    Und alles was dynamisch passiert, kann AFAIK verzögert werden, bis die erste Funktion einer TU aufgerufen wird.

    Dahier ist auch die Aussage von Dravere

    Bei der Lösung von rean, wird die init-Funktion garantiert gleich beim Programmstart aufgerufen, weil der zero-initiliser vom struct der Defaultkonstruktor ist.

    nicht richtig, da dies ja nicht "static initialization" ist.


  • Administrator

    @pumuckl,
    8.5 Abschnitt 5
    "To zero-initialize an object of type T means:"
    ...
    "— if T is a non-POD class type (clause 9), the default constructor for T is called (and the initialization is ill-formed if T has no accessible default constructor);"

    Also ich weiss echt nicht. Ich fand das schon immer sehr verwunderlich, aber wenn da explizit steht, dass vor jeglicher Initialisierung eine zero-Intialisierung folgt. Wie soll man diesen Satz denn sonst verstehen?

    hustbaer schrieb:

    Bei der Lösung von rean, wird die init-Funktion garantiert gleich beim Programmstart aufgerufen, weil der zero-initiliser vom struct der Defaultkonstruktor ist.

    nicht richtig, da dies ja nicht "static initialization" ist.

    Wieso nicht? Das Objekt rfi ist static in der Klasse.

    Grüssli



  • @Dravere:

    Wieso nicht? Das Objekt rfi ist static in der Klasse.

    Das "static" in "static initialization" und das "static" in "static storage duration" sind zwei gänzlich unterschiedliche "static" - eins hat mit dem anderen nichts zu tun.

    Ich kann im Moment leider nicht lustig rumzitieren (sitz grad nicht vor meinem eigenen PC, wo ich den Standard zu Hand hätte)... wenn ich dran denke schlag ich nachher die relevanten Stellen nach und poste sie.



  • Wird die Sachlage dadurch geändert dass man ein const vor das ganze stellt?

    class foo
    {
    //....
        static const struct run_foo_init{ run_foo_init(){ foo::foo_init(); } } rfi;
    };
    //...
    
    const foo::run_foo_init foo::rfi;
    


  • Verzweifelnder schrieb:

    Hallo,
    ich habe eine Klasse geschreiben (von der es auch mehr als nur ein Objekt geben kann / keine Singleton-Klasse). Damit diese Klasse richtig funktioniert muss einmal am Anfang vom Programm eine Funktion aufgerufen werden (aber nur EINMAL im Programm, nicht einmal pro Objekt). Muss ich das nun wirklich an den Anfang der main-function schreiben (gefällt mir überhaupt nicht, weil die Funktion ja mit der Klasse in Verbindung stehen sollte)??

    Wozu braucht man sowas?


Anmelden zum Antworten