Programm sauber beenden



  • Hi,

    ich hab mir eine Klasse geschrieben, die eine Anwendung kapselt (TApplication).
    Um eine Anwendung zu starten, muss also ein Instanz dieser Klasse erzeugt werden.
    Mein Problem ist nun, das Programm sauber zu beenden, falls meine Anwendung ein Signal (SIGINT) erhält.
    Ich hab eine Funktion registriert, die sich um die Behandlung dieses Signals kümmert (mir geht's nicht um das Abfangen der Signale, sondern um das saubere Aufräumen)
    Ich würde aber gerne sicherstellen, das alle Resourcen wieder freigegeben werden (Destruktoren sollen ausgeführt werden).

    Damit das in der Methode klappt, die sich um das Signal kümmert und ich nicht mit einem globalen Anwendungsobjekt rumeiere, existiert in TApplication eine statische Methode, die einen Pointer auf das Anwendungsobjekt zurückliefert.
    Diesen wollte ich dann mit delete freigeben.
    Damit sollten dann der Destruktor meiner Anwendung und die darin enthalten Objekte zerstört werden.
    Ist das ein gangbarer Weg oder gibt's da bessere Mechanismen?

    Beispiel (einfach mal schnell runtergetippt):

    class TApplication
    {
        public:
            //...
            static TApplication * instance();
            //...
    };
    
    void strg_c(int sig)
    {
        // Anwendungsobjekt abräumen
        if(sig == SIGINT)
            delete TApplication::instance();
    }
    
    int main()
    {
        // strg_c() registrieren
    
        // Anwendung erzeugen und starten
        TApplication::createApplication();
        return TApplication::instance()->execute();
    }
    


  • Ach, noch ein Nachtrag.

    In der Funktion die das Signal behandelt, hatte ich zuerst ein exit(1) eingebaut. Dann wird aber kein Destruktor aufgerufen.
    Dann bin ich auf die tolle Idee gekommen, eine Exception zu werfen.
    Damit wurden zumindest die Destruktoren durchlaufen 😃
    Allerdings hat eine Exception da nun wirklich nichts verloren, daher bin ich auf die oben beschriebene Vorgehensweise gewechselt.

    Die scheint was besser zu sein. Falls es noch andere Möglichkeiten gibt, immer raus damit. 😉



  • Sauber beenden geht in C++ eigentlich nur indem man dafür sorgt dass main() verlassen wird. Dazu kenne ich nur eine einzige Möglichkeit, die aber leider auch ihre Tücken hat, und zwar indem man eine Exception wirft.

    class TApplication
    {
    // ...
    };
    
    class SigintException {};
    
    void strg_c(int sig)
    {
        if(sig == SIGINT)
            throw SigintException();
    }
    
    int main()
    {
        // strg_c() registrieren
    
        try
        {
            TApplication app;
            app.execute();
        }
        catch (SigintException const&)
        {
            return 3;
        }
    
        return 0;
    }
    

    Das Problem dabei ist dass sonst an *nirgends* jemand eine SigintException fangen darf, sonst wird das Programm einfach nicht beendet.
    Blöderweise gibt es in einigen Libraries "catch (...)" Konstrukte, die halt einfach alles fangen, ... und das war's dann mit "programm wird beendet".

    Natürlich kannst du zusätzlich in strg_c() eine globale Variable setzen die du dann an mehreren Stellen immer wieder abfragst, und wenn sie != 0 ist halt dort wieder eine Exception werfen oder sowas.

    Oder du verlässt dich einfach darauf dass die std. Library + das OS alles "ausreichend" gut saubermachen. Im Normalfall ist es auch ausreichend.
    Wenn du bestimmte Dinge hast die die std. Library + das OS "übersehen" kannst du immer noch eine Funktion mit atexit() registrieren, und in strg_c() einfach exit() aufrufen:

    MSDN schrieb:

    exit calls, in last-in-first-out (LIFO) order, the functions registered by atexit and _onexit, then flushes all file buffers before terminating the process

    Dabei ist es allerdings IMHO ein kleines Problem für diese mit atexit() registrierten "Saubermach-Funktion" sicherzustellen dass sie auch alles in einem konsistenten Zustand antrifft so dass sie auch "sauber saubermachen" kann.
    Dazü würde ich auf jeden Fall NICHT gleich das ganze Applikations-Objekt zerstören, sondern nur bestimmte Dinge tun die wirklich notwändig sind. Davon abgesehen ist es nicht leicht sicherzustellen dass die Daten auf die diese Funktion zugreift immer konsistent sind, denn das Signal kann jederzeit kommen, ganz egal was gerade ausgeführt wird.

    ----

    Ich würde es auf jeden Fall entweder kooperativ lösen (also mittels Flag welches du selbst prüfst), oder das Signal gleich ganz deaktivieren, oder die Standardbehandlung lassen und garnix ändern.



  • Hi,

    danke für deine Antwort.
    Ich hoffe ich hab dch richtig verstanden. Dir ist auch keine Möglichkeit bekannt, ein Programm so zu beenden, das dabei die Destruktoren aller noch lebendender Objekte abgearbeitet werden?
    Den Ansatz mit der Exception hatte ich mal ausprobiert, allerdings schien mir das nicht wirklich sauber zu sein. Du hast da aber recht, mit dieser Methode werden dei Destruktoren ausgeführt.
    Jetzt steh ich vor dem Problem, das ich immer nach dem Motto "Resourcenbelegung ist Initialisierung" vorgegangen bin, und dadurch eben sehr viele "Resourcenobjekte" rumstehen hab.
    Wenn es sich dabei nur um angeforderten Speicher drehen würde, wäre mir das noch recht egal und ich würd's dem OS überlassen. Was aber wenn temp. Dateien angelegt werden?
    Diese sollten eigentlich auch in den Destruktoren wieder gelöscht werden.
    Danke für deine Antwort. Mal sehen wie ich das Problem gelößt bekomme.

    EDIT:
    Mit einem globalen Flag kann ich das leider nicht lösen, da ich die Kontrolle an eine Methode der spezialisierten Anwendungsklasse abgebe.



  • Was soll an dem Werfen einer Exception "unsauber" sein? Es ist nur "unsicher" weil sie ja jemand zu früh fangen könnte...

    Was die Temp-Files angeht: entwirf dein Programm IMMER so dass es auch noch brauchbar funktioniert wenn es durch z.B. einen Stromausfall oder Coredump unterbrochen wurde. Dann sorgen die gleichen Mechanismen die du dafür gebaut hast auch dafür dass es noch brauchbar funktioniert wenn es durch ctrl+c unterbrochen wurde.
    Bei Temp-Files könnte man z.B. einfach ein spezielles Namens-Schema verwenden, also die Dateien alle LaLeLubb-%08x%08x oder so nennen. Dann kannst du bei jedem Programmstart einfach alle "LaLeLubb-*" Dateien löschen. Oder du legst ein eigenes Verzeichnis für die Temp Files an.
    Oder du führst in irgendeiner Datei eine Liste offener Temp-Files mit. Irgendetwas in der Art.

    BTW: noch ein Grund ctrl+c nicht selbst zu behandeln: der default Handler für Signale kommt mit fast jeder verfahrenen Situation klar, und das ist gut, da man dadurch fast jedes Programm in jedem Zustand noch mit ctrl+c beenden kann. Wenn du den Handler überschreibst wirst du die Sache fast sicher schlechter machen was das angeht, denn sobald du versuchst selbst irgendwas sauberzumachen wird es wahrscheinlich fälle geben in denen der cleanup Code Mist baut, in eine Endlosschleife geht, sonst irgendwie stecken bleibt, oder einen Coredump hinlegt.

    Von daher: lass es einfach so wie es ist.


Anmelden zum Antworten