Init Funktion, Konstruktor, App-Klasse



  • manni66 schrieb:

    one two three schrieb:

    Ich mag keine Init-Funktionen, dafür sind Konstruktoren da!

    👍

    Aber eine App-Klasse, die alle Objekte enthält? Warum? Was ist schlecht am Heap, wenn man RAII benutzt?

    Vielleicht befürchtet man unvorhersagbares Zeitverhalten bei der Speicheranforderung. Es gibt keine Garantie, wie schnell das BS einen passenden Block findet. Vielleicht hat man irgendwo mal gehört, dass malloc in Echtzeitanwendungen böse ist.

    Völlig aus der Luft gegriffen ist das ja auch nicht, und wenn man harte Echtzeitanforderungen hat und die Möglichkeit besteht, alle jemals benötigten Resourcen statisch bereitzustellen, dann ist das nicht die schlechteste Idee. Ab einer gewissen Komplexität der Software ist das bloß nicht mehr praktikabel.

    Aber wenn man alles erst default-konstruiert (mit null_ptr und so weiter) und später, wenn die nötigen Daten verfügbar sind, nochmal "nach-initialisiert", dann gibt es genausowenig eine Garantie, dass das echtzeitgerecht passieren wird. Es ist egal, ob man spät konstruiert oder spät initialisiert.

    Und den grundlegenden Unterschied zwischen global App app und global App* pApp verstehe ich auch nicht.

    Konstruktoren (ich meine die richtigen mit den erforderlichen Parametern) haben gegenüber Init-Funktionen den Vorteil, dass sie garantiert auf einem Objekt genau einmal aufgerufen werden. Init-Funktionen können mehrfach ausgeführt oder auch ganz vergessen werden.



  • one two three schrieb:

    Berechtigter Einwand ist sicher gegen eine fette Application-Klasse.

    Jo, ein Default-Konstruktor macht sicher nur das Nötigste, aber ich finde...ein Defaultkonstruktor der App sollte dann auch nur die Member-Klassen default konstruieren.

    Sinn eines Konstruktors ist, ein Objekt in einen definierten Anfangszustand zu bringen, der keine Zufälligkeiten enthält. Insofern ist ein Default-Konstruktor, der alles auf Null setzt, besser als nichts.

    Andererseits ist ein parametrisierter Konstruktor aber erheblich besser als ein Default-Konstruktor, der ein halbfertiges Ding hinterlässt, an dem nachträglich noch jede Menge Hand angelegt werden muss.

    Die "große App-Klasse" sollte daher meiner Meinung nach keinen Default-Konstruktor haben, sondern der Programmierer sollte gezwungen werden, soviel wie möglich betriebsfertig hineinzukonstruieren.

    Kann man die Teile, die später dazukommen, vielleicht extern konstruieren und über Add-Methoden hineingeben?



  • Printe schrieb:

    Und den grundlegenden Unterschied zwischen global App app und global App* pApp verstehe ich auch nicht.

    Nun ja, ich hatte im statischen Speicher nur Zeiger haben wollen. Denn alles was im statischen steht wird ja ins Image "gebrannt". Und da Speicher aus der Sicht des Speichers eben Speicher ist, sollte der Heapspeicherzugriff auch nicht wesentlich langsamer sein als der Statische. Der statische Speicher und der Stack-Speicher sind, wenn ich recht informiert bin, größenbeschränkt. Der Heap nicht.

    Die Zeiger können dann, recht easy, in globalen UI-Funktion des Betriebssystems genutzt werden.

    Und das die App-Klasse jemals so groß wird (mit allem Drum und Dran, Buffer,...), das sie als Speicherblock nicht mehr allokiert werden kann, denke ich auch nicht.

    Un ja, das Programm sollte soviel Speicher allokieren wie möglich (benötigt), damit spätere oder immer wiederkehren Operationen nicht permanent...Speicher allokieren...Speicher deallkokieren müssen.

    Die erste Idee in der Besprechnung war eben...eine App-Klasse zu bauen und diese dann in den statischen Speicher zu hauen, alle Member dann eben auch in den statischen...damit die Anwendung schneller läuft.

    Und so kam die Idee mit Init-Funktionen auf...die ich, wie schon erwähnt, für die blödeste aller Zeiten halte.



  • one two three schrieb:

    Nun ja, ich hatte im statischen Speicher nur Zeiger haben wollen. Denn alles was im statischen steht wird ja ins Image "gebrannt". Und da Speicher aus der Sicht des Speichers eben Speicher ist, sollte der Heapspeicherzugriff auch nicht wesentlich langsamer sein als der Statische. Der statische Speicher und der Stack-Speicher sind, wenn ich recht informiert bin, größenbeschränkt. Der Heap nicht.

    Ähm ... wie meinen? Ins Image gebrannt? Größenbeschränkt?



  • Ich dachte immer nur der Heap kann beliebig wachsen, solange bis das Betriebssystem abkackt und keinen mehr anfordern kann (hängt sicher auch von der Größe des Blocks ab).

    Static- und Stackspeicher kann nicht unbegrenzt wachsen.

    Der "Static" ist doch in der Exe (Image). Es wird doch beim Start des Prozesses automatisch allokiert...



  • Irgendwie liest sich das für mich etwas wirr.

    Meiner Meinung nach:
    - Global nur wenn nötig. Warum das nötig sein sollte, erschließt sich mir grade noch nicht ganz.

    - Default Konstruktoren nur wenn sinnvoll, sonst nicht bereit stellen.

    - Init Funktionen können Sinvoll sein, wenn's auf dem Stack sein soll/muss und zum Zeitpunkt der Konstruktion noch nicht alles fest steht, (z.B. weil es von User eingaben o.ä. abhängt). Eigentlich ziehe ich dafür dann aber Heap und entsprechende Konstruktoren vor.

    Ansonsten, Speicher auf Heap / Stack, Performance Optimierung etc. hängen gewaltig von der konkreten Anwendung ab. Es kann ja auch sein, dass die Anwendung viel Speicher braucht, da will man schon mal Speicher zwischendurch wieder frei geben.



  • Schlangenmensch schrieb:

    Irgendwie liest sich das für mich etwas wirr.

    Meiner Meinung nach:
    - Global nur wenn nötig. Warum das nötig sein sollte, erschließt sich mir grade noch nicht ganz.

    Von z.B. Windows ausgehend haben UI-Anwendungen Callback-Funktionen (Fensterprozedur). Da das Anwendungsobjekt der Ansprechpartner für eben diese Funktionen sein soll (z. B. die Ereignissbehandlung) dachte ich mir das es schlau wäre zumindest den Zeiger auf das Anwendungsobjekt global zu machen.

    Das es sich wirr liest liegt auch daran das ich so meinen Gedanken zur Projektrealisierung freien Lauf lasse. Ich jedoch wesentlichen Aspekten zur Umsetzung nicht zustimme, wie aus dem Kontext der Frage / Antworten hervorgeht 😃



  • Bist du der gleiche, der auch Framework design fürs Spiel gepostet hat?



  • Nein 😮



  • So, habe mich mal unter meinem registrierten Nick angemeldet 😉

    Das Projekt an dem ich arbeite, bekommt Daten von einem anderen Gerät (bzw. mehreren Geräten), diese werden verarbeitet und sollen auf einem Server gespeichert werden und auf dem Client sichtbar gemacht werden.
    Derzeit wird das Framework ausgehandelt. Ich kann bei Null beginnen...steht das Framework, übernehme ich die Windows-Implementierung. Dann verlasse ich das Projekt und "altgediente" Mitarbeiter übernehmen dann die Linux-Implementierung.

    Den Post über das "Spieleframedingsdesign" hatte ich auch gesehen.



  • hab mal ein bisschen vor mich hin gedacht:

    //#define BSS
    
    class App
    {
    public:
        virtual ~App() = default;
    
    public:
        virtual void Run() = 0;
    };
    
    class AppImpl: public App
    {
    public:
        AppImpl();
    
    public:
        virtual ~AppImpl();
    
    public:
        virtual void Run();
    };
    
    AppImpl::AppImpl()
    {
    
    }
    
    AppImpl::~AppImpl()
    {
    
    }
    
    void AppImpl::Run()
    {
    
    }
    
    #ifdef BSS
    namespace global{
        AppImpl* app_impl;
        App *app;
    }
    
    #else //DATA
    namespace global{
        AppImpl app_impl;
        App *app = &app_impl;
    }
    #endif
    
    int WINAPI WinMain(
                    HINSTANCE,
                    HINSTANCE,
                    LPSTR,
                    int)
    {
    #ifdef BSS
        global::app_impl = new AppImpl();
        global::app = global::app_impl;
    #endif
    
        global::app->Run();
    
    #ifdef BSS
        delete global::app_impl;
    #endif
    
        return EXIT_SUCCESS;
    }
    

    Wenn BSS nicht definiert ist dann werden der initialisierten Zeiger ins DATA Register geschrieben.

    global::app->Run();
      bf:   48 8b 05 00 00 00 00    mov    0x0(%rip),%rax        # c6 <WinMain+0x1f>
                            c2: R_X86_64_PC32       .data
      c6:   48 8b 00                mov    (%rax),%rax
      c9:   48 83 c0 10             add    $0x10,%rax
      cd:   48 8b 00                mov    (%rax),%rax
      d0:   48 8b 15 00 00 00 00    mov    0x0(%rip),%rdx        # d7 <WinMain+0x30>
                            d3: R_X86_64_PC32       .data
      d7:   48 89 d1                mov    %rdx,%rcx
      da:   ff d0                   callq  *%rax
    

    wenn allerdings BSS definiert ist, dann sind die Zeiger uninitilaisiert (bzw wenn sie NULL sind)

    global::app->Run();
      ef:   48 8b 05 08 00 00 00    mov    0x8(%rip),%rax        # fe <WinMain+0x57>
                            f2: R_X86_64_PC32       .bss
      f6:   48 8b 00                mov    (%rax),%rax
      f9:   48 83 c0 10             add    $0x10,%rax
      fd:   48 8b 00                mov    (%rax),%rax
     100:   48 8b 15 08 00 00 00    mov    0x8(%rip),%rdx        # 10f <WinMain+0x68>
                            103: R_X86_64_PC32      .bss
     107:   48 89 d1                mov    %rdx,%rcx
     10a:   ff d0                   callq  *%rax
    

    Liegt z. B. die fette App-Klasse z.B. irgendwo im Heap, dann werden die Zeiger über das DATA Register angesprochen. Das würde aber bedeuten das ich immer über die VTable gehe und über den Zeiger auf die App...
    In einer Loop habe ich also sehr viele Referenzierungen.

    Initialisiere ich das globale App-Objekt auch global, dann brauche ich wahrscheinlich die Init-Funktion...habe aber weniger Dereferenzierungen...
    wenn es darauf ankommt das der Code so schnell ist wie möglich


  • Mod

    FrankTheFox schrieb:

    Das würde aber bedeuten das ich immer über die VTable gehe und über den Zeiger auf die App...

    Weil der Compiler aus dem Typ des Zeigers nicht bereits beom Compilieren auf den dynamischen Typ des Objektes dahinter schließen kann, und daher nicht weiss, welcher Override der Funktion am Ende zuständig ist. Für diesen Zweck gibt es seit C++11 final.

    Mit der Frage, wo sich das Objekz befindet hat das nicht direkt etwas zu tun.

    class AppImpl: public App
    {
    ... 
    public:
        virtual void Run() final;
    };
    


  • Jo, stimmt. Mit Optimierungen seitens des Compilers ist dann auch der Vtable-Zugriff fast weg.

    Ich befürchte nur, hängt aber dann auch vom späteren Design ab das ich massig Dereferenzierungen habe und das dann Zeit kostet.

    eine "große" Application-Klasse bietet für mich jedoch Vorteile:

    - Ich kann alle Handles belegen.
    - Hier habe einen zentralen Datentopf.
    - RAII
    - Ich kann die EventLoop / MessageLoop verstecken und die Klasse
    Eventabhängig machen (z. B. sollte der User Eingaben vornehmen wollen 😉 )
    - ein zentrales "Cleanup"
    - OS abhängiges Zeugs ausführen.
    - Member als automatische Variablen

    Nachteile:

    - ein großer Speicherblock, der angefordert wird.
    - kann schnell unübersichtlich werden.


Anmelden zum Antworten