noexcept in library, wann und wo nutzen



  • @5cript sagte in noexcept in library, wann und wo nutzen:

    • Mir ist noexcept in vielen Fällen zu gefährlich, wenn die Funktion nicht super einfach ist, dann muss ich die Exceptionfreiheit beweisen, damit ich nicht die Anwendung abreiße wenn doch eine fliegen kann.

    Ich lege zwar auch grossen Wert auf Korrektheit der noexcept-Angabe, aber wenn ich ehrlich bin, dann haben unbehandelte Exceptions in den meisten Programmen, an denen ich arbeite, nahezu den selben Effekt wie ein terminate(). Die Exceptions werden halt noch vom äußersten Handler gefangen und produzieren eine UI- oder Log-Nachricht. Aber "crashen" tut das Programm dennoch.

    Zum Glück ist eine fehlerhafter noexcept-Specifier nicht UB - das wär übel 😱



  • @Finnegan sagte in noexcept in library, wann und wo nutzen:

    aber wenn ich ehrlich bin, dann haben unbehandelte Exceptions in den meisten Programmen, an denen ich arbeite, nahezu den selben Effekt wie ein terminate().

    Die haben nicht nahezu den Effekt, die rufen terminate() auf.



  • @Finnegan sagte in noexcept in library, wann und wo nutzen:

    Zum Glück ist eine fehlerhafter noexcept-Specifier nicht UB - das wär übel 😱

    Das war auch meine Befürchtung. Daher meine Frage.

    Aus der Literatur und aus euren Hinweise nehmen ich mir jetzt folgendes mit:

    • noexcept ( false ) -> Verwende ich nicht
    • noexcept -> Verwende ich bei Ctr/Dtr/Move/Copy sofern möglich
    • an anderen Stellen verwende ich noexcept nicht.

    Vielen Dank für eure Hinweise. Ihr habt mir geholfen 🙂



  • Bei Templates mit einem Member T kann man im allgemeinen davon ausgehen, dass selbst bei Default-Konstruktor noexcept nicht verwendet werden kann, weil man über T niemals eine Aussage treffen kann, richtig?



  • Wie ist eigentlich das Verhalten beim Vererben von z.B. Move-Assignment-Operator?
    Wenn ich z.B. von std::vector ableite und dort der Move-Assignment-Operator noexcept ist, ist dann mein compiler-generierter Move-Assignment-Operator ebenfalls automatisch noexcept oder muss ich die Dinger dann selbst definieren nur für das noexcept ?



  • @It0101 sagte in noexcept in library, wann und wo nutzen:

    Bei Templates mit einem Member T kann man im allgemeinen davon ausgehen, dass selbst bei Default-Konstruktor noexcept nicht verwendet werden kann, weil man über T niemals eine Aussage treffen kann, richtig?

    Ja, außer Du schränkst die Typen mit Hilfe von Concepts ein.

    @It0101 sagte in noexcept in library, wann und wo nutzen:

    • noexcept -> Verwende ich bei Ctr/Dtr/Move/Copy sofern möglich

    Sofern allokiert wird gilt Folgendes:
    Nein, bei Move (gilt genauso für Copy, MoveAssignment, CopyAssignment) auf keinen Fall, außer man weiß, dass der Allokator sich gutmütig verhält (z.B. std::allocator), weil jeder dieser Vorgänge bei einem beliebigen Allocator eine Reallokation auslösen kann und die kann prinzipiell eine Exception werfen. Wenn es ein anderer Allokator ist, wird nicht wirklich ein Move gemacht, sondern eine CopyConstruction und eine anschließende Destruktion der Quelle. Das ist Notwendig, weil nur so Daten aus einem besonderen Speicher in einen anderen verschoben werden können. Die Norm ist an dieser Stelle defekt, da sie zwar solche Allokatoren erlaubt, aber andererseits erfordert, dass ein Move O(1) ist und die Zeiger sich nicht verändern. 🤦♂ D.h. alle Container der Standard Library haben UB mit Allokatoren die Allocator<T>::propagate_on_container_move_assigment als std::false_type definieren.



  • @Tyrdal sagte in noexcept in library, wann und wo nutzen:

    @Finnegan sagte in noexcept in library, wann und wo nutzen:

    aber wenn ich ehrlich bin, dann haben unbehandelte Exceptions in den meisten Programmen, an denen ich arbeite, nahezu den selben Effekt wie ein terminate().

    Die haben nicht nahezu den Effekt, die rufen terminate() auf.

    Ich meinte das aus der Perspektive des Anwenders eines solchen Programms:

    Wenn eine Exception an einer Stelle fliegt, wo ich keine erwartet habe, dann sieht er die eine Meldung á la "WTF? Sorry! Das hätte nicht passieren dürfen. Bye!".

    Wenn ich schon einen noexcept-Specifier verwende, dann rechne ich dort auch nicht mit einer Exception, d.h. die wird ohnehin nur durch äußersten Handler behandlelt, der obige Nachricht ausgibt.

    Bei terminate() steigt das Programm eben aus, ohne sich vorher höflich zu verabschieden.

    Natürlich kann es für den Anwender noch Unterschiede bezüglich Ressourcen geben, die nicht vom OS aufgeräumt werden, wenn der Prozess abgeschossen wird. Das ist aber wahrscheinlich eher selten der Fall.



  • Übrigens - ein paar design-philosophische Gedanken, die ich vor einiger Zeit schonmal erwähnt habe: Ich glaube der häufigste Grund, der einem noexcept im Wege steht, dürften wohl irgendwelche Speicher-Allokationen sein. Da sollte es - wenn ich nichts übersehen habe - 3 Ursachen geben, weshalb eine Exception fliegen kann:

    1. Ich denke in einer echten OOM-Situation wird man eine std::bad_alloc-Exception ohnehin kaum sinnvoll behandeln können. Ausser vielleicht mit einem sehr speziellen, auf diese Situation zugeschnittenen Handler.

    2. Ein zu großer Speicherblock und dann einen kleineren versuchen ließe sich sicher auch auf andere Weise behandeln - new (nothrow) oder Allocator-Aufrufe in ein try_allocate-Funktion verpacken, welche die Exception intern behandelt.

    3. Und schliesslich schlichtweg fehlerhafte Parameter für Allokations-Funktionen. Diese sind m.E. Programmierfehler, die man auch mit Assertions abfangen kann.

    Wenn man bei 2+3 auf Exceptions verzichten kann und bei 1 ohnehin nur in seltenen Fällen wirklich behandeln will oder kann, könnte man dort vielleicht auch ein terminate() akzeptieren, wodurch vermutlich die allermeisten Funktionen, die heute potentiell werfen können noexcept deklariert werden könnten.

    Vielleicht könnte man sowas über ein Compiler-Flag Opt-in machen. Besonders wenn ich mir E.12 der C++ Core Guidelines und die Diskussion dazu anschaue ist das keine so abwegige Idee. Die Empfehlung ist dort, noexcept nicht nur dann zu verwenden, wenn nachweislich keine Exception fliegen kann, sondern auch, wenn man eine Exception nicht (sinnvoll) behandeln kann oder will und bereit ist, diese wie einen kritischen Hardwarefehler zu betrachten.

    Da @It0101 allerdings wie eingangs erwähnt an einer Bibliothek arbeitet, möchte ich das hier nur als allgemeinen Diskussionsbeitrag verstanden wissen. Ob ein terminate() akzeptabel ist, ist eine Entscheidung, die dem Anwender einer Bibliothek vorbehalten bleiben sollte. In Bibliotheks-Code würde ich auch "nachweislich keine Exception" als Maßstab empfehlen.



  • @Finnegan sagte in noexcept in library, wann und wo nutzen:

    1. Ich denke in einer echten OOM-Situation wird man eine std::bad_alloc-Exception ohnehin kaum sinnvoll behandeln können. Ausser vielleicht mit einem sehr speziellen, auf diese Situation zugeschnittenen Handler.

    Dank Memory Overcommitment wirst Du in einem echten OOM-Fall gar keine Exception sehen, da erst bei Zugriff auf die Pages das OS feststellt, dass es nicht mehr ausreichend RAM hat. Auf einem UNIX/Linux siehst Du da einen SegFault, und das Programm kann da gar nichts mehr machen. Was man darüber nur abfangen kann, sind unrealistisch große Speicheranforderungen, da gibt das OS noch eine negative Rückmeldung.



  • @john-0 sagte in noexcept in library, wann und wo nutzen:

    @Finnegan sagte in noexcept in library, wann und wo nutzen:

    1. Ich denke in einer echten OOM-Situation wird man eine std::bad_alloc-Exception ohnehin kaum sinnvoll behandeln können. Ausser vielleicht mit einem sehr speziellen, auf diese Situation zugeschnittenen Handler.

    Dank Memory Overcommitment wirst Du in einem echten OOM-Fall gar keine Exception sehen, da erst bei Zugriff auf die Pages das OS feststellt, dass es nicht mehr ausreichend RAM hat. Auf einem UNIX/Linux siehst Du da einen SegFault, und das Programm kann da gar nichts mehr machen. Was man darüber nur abfangen kann, sind unrealistisch große Speicheranforderungen, da gibt das OS noch eine negative Rückmeldung.

    Ja, vielleicht noch durch vom System forcierte Speicher-Limits, oder wenn man eine Malloc-Implementierung verwendet, der man eine Obergrenze mitgeben kann. Eigentlich ein Argument mehr, dass man in dem Fall auch ein terminate() akzeptabel sein könnte. Schlechter als ein SegFault ist das nicht.

    Sicher gibts auch Szenarios, wo man die Exceptions haben will, aber die Menge an Funktionen, die noexcept sein könnten, wenn bad_alloc wegfällt und fast überall z.B. bei std::move_if_noexcept gemoved werden kann ist nicht zu verachten.

    Würde mich jedenfalls mal interessieren, was sich damit im Schnitt herausholen ließe. Besonders bei größerem Legacy-Code, der gar nichts noexcept deklariert und viel in Containerklassen herumschiebt. Zumindest wenn die Klassen überhaupt effizient gemoved werden können, z.B. mit vielen std::string oder std-Container Daten-Membern.


Anmelden zum Antworten