Programm sauber in Funktion beenden



  • Hallo,
    ich sehe folgende Möglichkeiten, ein Programm aus einer Funktion heraus zu beenden:

    • exit() nutzen:
      Vorteil: einfach zu nutzen
      Nachteil: Destruktoren werden nicht aufgerufen und Ressourcen nicht freigegeben (wobei sich manche Quellen da auch widersprechen)
    • Exception werfen und in main() abfangen
      Vorteil: einfach
      Nachteil: ein einzelnes catch(...){} kann schnell Ärger verursachen
    • weiteren Parameter/Rückgabewert, der gegebenenfalls einen Abbruch signalisiert
      Vorteil: leicht verständlich, sauber
      Nachteil: kompliziert umzusetzen

    Habe ich noch eine Möglichkeit übersehen? Was haltet ihr für die beste Option?
    Derzeit wird exit() verwendet.

    Danke!



  • https://en.cppreference.com/w/cpp/utility/program/exit

    Stack is not unwound: destructors of variables with automatic storage duration are not called.

    Warum soll das Programm in einer Funktion abgebrochen werden?



  • Die sauberste Variante ist immer noch ein return aus der main(), so beende ich alle meine Programme. Beschreibe mal grob dein Programm und die Funktion, wo das passieren soll. Vielleicht haben wir ein paar Tips wie man das am besten hinbekommt.



  • @manni66 sagte in Programm sauber in Funktion beenden:

    https://en.cppreference.com/w/cpp/utility/program/exit

    Stack is not unwound: destructors of variables with automatic storage duration are not called.

    Darauf habe ich mich auch verlassen. Deswegen würde ich das ja gerne ändern, dieser Teil des Codes ist nämlich ursprünglich nicht von mir.
    Es finden sich zu exit() aber auch Quellen, die besagen, dass es einem einfachen "return 0" in main() entspricht.

    @Finnegan sagte in Programm sauber in Funktion beenden:

    Die sauberste Variante ist immer noch ein return aus der main(), so beende ich alle meine Programme.

    So wird das Programm natürlich auch in den Möglichkeiten 2 und 3 beendet. Es geht ja nur darum, dass der main()-Funktion signalisiert werden muss, dass das Programm beendet werden soll.

    Beschreibe mal grob dein Programm und die Funktion, wo das passieren soll. Vielleicht haben wir ein paar Tips wie man das am besten hinbekommt.

    Ist eine einfache Konsolenanwendung mit internem Prompt. Dabei ist die Schleife für Promt und Befehlseingabe in einer anderen Funktion. Befehlsanalyse ist wiederum in einer anderen Funktion - und daraus heraus soll das Programm normal beendet werden können. Weiterhin soll es dann noch einmal 3-4 Funktionen tiefer noch einmal normal beendet werden können und einmal noch im Fehlerfall abgebrochen werden.
    Ich habe gerade noch einmal nachgesehen, es existiert an einigen Stellen catch(...), das sollte sich aber umschreiben lassen.



  • Mach ne struct quit_exception {}; vielleicht ohne abzuleiten und schiess damit rum. Ressourcen müssten am Ende des Programms doch eh freigegeben werden?



  • @forbidden

    Auch die wird mit (...) gecatched. Hat der TE aber doch schon selber bedacht.
    Die Exception-Lösung finde ich sogar am schlechtesten.



  • @Unterfliege sagte in Programm sauber in Funktion beenden:

    Es finden sich zu exit() aber auch Quellen, die besagen, dass es einem einfachen "return 0" in main() entspricht.

    Das ist bestenfalls für C der Fall. Habe schon Ärger mit ein eingebundenen C-Bibliotheken gehabt, die meinten, es sei eine gute Idee exit() aufzurufen. Destruktoren werden definitiv nicht garantiert ausgeführt.

    @Finnegan sagte in Programm sauber in Funktion beenden:

    Die sauberste Variante ist immer noch ein return aus der main(), so beende ich alle meine Programme.

    Ist eine einfache Konsolenanwendung mit internem Prompt. Dabei ist die Schleife für Promt und Befehlseingabe in einer anderen Funktion. Befehlsanalyse ist wiederum in einer anderen Funktion - und daraus heraus soll das Programm normal beendet werden können. Weiterhin soll es dann noch einmal 3-4 Funktionen tiefer noch einmal normal beendet werden können und einmal noch im Fehlerfall abgebrochen werden.

    Wie werden denn die Zustände repräsentiert, die mit im Prompt eingegebenen Befehlen verändert werden? So á la prompt> fahre klappen aus und klappen_ausgefahren = true? Eigentlich ist "Programm soll beendet werden" ja auch nur ein solcher Zustand. Wäre bei Änderungen an einem vorhandenen Programm sinnvoll, das genau so zu handhaben, wie es bereits jetzt schon gemacht wird. programm_beenden == true sorgt dann eben dafür, dass aus allen Loops ausgestiegen wird. Oft ist programm_beenden ein Signal/Message, das im Event-Loop in der main() behandelt wird, es geht aber auch mit einem entsprechenden Flag/bool in einem Zustandsobjekt.



  • @Jockelx sagte in Programm sauber in Funktion beenden:

    Die Exception-Lösung finde ich sogar am schlechtesten.

    Weshalb denn?

    @Finnegan sagte in Programm sauber in Funktion beenden:

    Wie werden denn die Zustände repräsentiert, die mit im Prompt eingegebenen Befehlen verändert werden? So á la prompt> fahre klappen aus und klappen_ausgefahren = true? Eigentlich ist "Programm soll beendet werden" ja auch nur ein solcher Zustand. Wäre bei Änderungen an einem vorhandenen Programm sinnvoll, das genau so zu handhaben, wie es bereits jetzt schon gemacht wird. programm_beenden == true sorgt dann eben dafür, dass aus allen Loops ausgestiegen wird. Oft ist programm_beenden ein Signal/Message, das im Event-Loop in der main() behandelt wird, es geht aber auch mit einem entsprechenden Flag/bool in einem Zustandsobjekt.

    Die main()-Funktion wird nach der Programmargumentanalyse und dem Aufruf der Schleifenfunktion nicht mehr betreten. Die entsprechenden command-functions werden direkt aufgerufen.
    Ich verstehe genau, was du meinst. Aber da würde ein Exceptionmechanismus eigentlich besser funktionieren als das manuell über if-Verzweigungen und irgendwelche bool-Werte zu machen. Ich bin auch bereits dabei, die ganzen catch(...) zu ersetzen. Es gibt nämlich eigentlich bereits eine ganze Menge an eigenen exception classes, die damit abgefangen werden sollen. Denen fehlte bisher nur eine gemeinsame base class.

    Also wenn es kein entscheidendes Argument gegen eine Exception gibt oder einen gänzlich anderen (besseren) Vorschlag gibt, werde ich mich wohl dafür entscheiden.



  • @Unterfliege sagte in Programm sauber in Funktion beenden:

    @Jockelx sagte in Programm sauber in Funktion beenden:

    Die Exception-Lösung finde ich sogar am schlechtesten.

    Weshalb denn?

    Weil ich Exceptions nicht zur Programmsteuerung missbrauchen wollen würde.
    Auch wenn du die catch(...)-Stellen jetzt alle raussuchst und irgendwie änderst, bleibt die doch Gefahr dass du (oder insb. jemand anderes) in einem Monat wieder irgendwo alles fängt und 2 Wochen später fällt auf, dass das Beenden nicht mehr funktioniert, obwohl seit Monaten niemand mehr irgendwas mit 'Beenden' gemacht hat.



  • @Jockelx sagte in Programm sauber in Funktion beenden:

    Weil ich Exceptions nicht zur Programmsteuerung missbrauchen wollen würde.

    Meistens ist es ein Designfehler im Programm, wenn man auf einmal die Abbruch bedingugn am einfachsten so löst. Das muss jedoch kein gravierender sein. Exceptions werfen und fangen ist sehr einfach und man kann diese auch fallunterscheiden. "Kann". man also machen.



  • @Unterfliege sagte in Programm sauber in Funktion beenden:

    Also wenn es kein entscheidendes Argument gegen eine Exception gibt oder einen gänzlich anderen (besseren) Vorschlag gibt, werde ich mich wohl dafür entscheiden.

    Wenn du's "Quick and Dirty" willst, dann tut es natürlich die Exception. Immerhin besser als irgendwo tief im Code verstreute exit() oder gar setjmp/longjmp(alles schon gesehen 😉 ).

    Programme, die das "von überall beenden" sauber implementiert haben, arbeiten jedoch so ziemlich alle mit einer einzigen Hauptschleife/Event-Loop, die einen Message-Queue abarbeitet. Kann verstehen, dass das nur für die eine Funktion zu aufwändig ist, aber generell würde ein Programm, das mit Nutzereingaben eine Maschine steuert (GUI wie auch Eingabe-Prompt), von so einerm Design profitieren. Da werden dann die Prompt-Eingaben nach dem parsen in Steuer-Nachrichten umgewandelt. Die "QUIT"-Nachricht ergibt sich da fast von selbst.

    Man kann die Kommandos auch in std::function verpackt auf nen vector/queue schieben und von der Hauptschleife abarbeiten lassen. die Quit-Message ist dann eben eine Funktion, die einen entsprechenden bool setzt, so dass die Hauptschleife verlassen wird. Kommt halt drauf an wie der ganze Code so strukturiert ist und man für sowas nicht zu viel umbauen muss.



  • Es gibt ja in dem Programm auch eine Hauptschleife, die befindet sich eben nur bereits in einer anderen Funktion. Alles weitere außer Eingabe wird aber in tieferliegenden Funktionen abgearbeitet. Für alleine den einfachen "quit" Befehl müsste ich also den bool bereits zwei Ebenen hochreichen.
    Da das gesamte Programm auch schon einen Exceptionmechanismus für die Programmsteuerung verwendet, werde ich das Verlassen jetzt vermutlich auch als Exception implementieren, die in main() abgefangen wird und mit dem entsprechenden Rückgabewert das Programm beendet wird.
    Wenn ich einmal mehr Zeit finden werde, werde ich das gegebenenfalls noch einmal schöner umschreiben.

    Danke euch allen!



  • Also bevor ich das mit Exceptions mache, würde ich eher noch ne globale Variable machen die dann einfach an der Stelle geprüft wird wo der nächste Befehl eingelesen würde.

    Oder das ganze gleich in ein Method-Object verwandeln, dann muss die Variable nichtmal global sein sondern wird eine Membervariable des Method-Object.



  • Du hast in letzter Zeit paar mal von Method-Objects gesprochen, und vor paar Jahren hast du mir sogar genauer erklärt, was du dir drunter vorstellt (find den Beitrag grad nicht), aber irgendwie kann ich mir immer noch schlecht vorstellen, was da jetzt für dich die entscheidende Eigenschaft ist. Mir ist das Pattern überhaupt nicht geläufig. Klar, bietet sich irgendwie mehr oder weniger an, die Logik in einer Klasse zu kapseln und eine Membervariable zu verwenden. Aber du redest explizit von Method-Objects, was war jetzt z.B. das ausschlaggebende Kriterium für dich, oder wo würdest du eine Grenze ziehen?



  • @Mechanics
    Hier ist das Pattern ganz gut beschrieben: http://wiki.c2.com/?MethodObject

    Die entscheidende Eigenschaft ist dass es eine Klasse gibt (=das "Method Object"), deren einzige Daseinsberechtigung es ist eine Methode einer anderen Klasse (oder eine freie Funktion) umzusetzen.

    Beispiel:

    static void some_local(...);
    static void helper_functions(...);
    
    int foo(lots of parameters) {
        // lots of code, passing lots of stuff to different helper functions
    }
    

    Mit einem Method Object können wir das jetzt erstmal so umschreiben:

    namespace {
    class foo_method {
    public:
        static int run(lots of parameters) {
            foo_method m;
            return m.run2(lots of parameters);
        }
    private:
        int run2(lots of parameters) {
            // lots of code, passing lots of stuff to different helper methods
        }
        void some_local(...);
        void helper_functions(...);
    };
    } // namespace
    
    int foo(lots of parameters) {
        return foo_method::run(lots of parameters);
    }
    

    Sieht jetzt erstmal nicht nach einem Gewinn aus. Allerdings haben wir jetzt die Klasse foo_method, und können anfangen bestimmte Dinge in Membervariablen abzulegen statt sie zwischen den ganzen Hilfsfunktionen rumzureichen. Was den Code drastisch vereinfachen kann.



  • @Unterfliege sagte in Programm sauber in Funktion beenden:

    Es finden sich zu exit() aber auch Quellen, die besagen, dass es einem einfachen "return 0" in main() entspricht.

    Seit C++11 muss man unterscheiden zwischen std::exit() und std::_Exit(). std::_Exit() ist seitdem dasselbe wie exit() aus der C Standard Library. Für std::exit() siehe [support.start.term]/9.



  • @hustbaer Ich hatte das auch so ähnlich in Erinnerung. Die public API von dem ganzen ist aber nach wie vor eine freie Funktion. Damit kommt man von "außen", also aus der main auch nicht an die Info ran, ob man jetzt beenden soll.
    Wär das in dem Fall so gedacht, dass "alles" in der Funktion bzw. method object dahinter, implementiert wird, die main nur noch die eine Funkton aufruft und über den Rückgabewert bestimmt, ob sie exit aufrufen soll?



  • @Mechanics Ja, genau, das Interface ändert sich dadurch nicht. Zumindest ist das mMn. die sinnvollere Variante, da ich der Meinung bin dass Method Object ein Implementierungspattern ist und daher nicht exposed werden sollte.

    Und in diesem Fall muss es ja wo eine Funktion geben die "bemerkt" dass der User "exit" eingegeben hat. Und dann muss es eine Funktion geben wo die Schleife drinnen ist die einen User-Befehl nach dem anderen einliest. Alles zwischen diesen beiden Funktionen (jeweils inklusive) würde ich in das Method Object packen.


Anmelden zum Antworten