Ausnahmesicherheit



  • Hallo

    Gegeben sei folgende Situation:

    Eine Klasse kann nur verwendet werden, wenn ich zuerst (vor weiteren Aktionen) eine Funktion einer C-Bibliothek aufgerufen habe. Der Aufruf dieser Funktion kann auf zwei Arten schief gehen:
    1. Es ist zu wenig Speicher verfügbar
    2. Es geht wegen einem anderen Faktor nicht (irrelevant hier zu nennen, kann aber direkt vom User beeinflusst werden)

    Wie soll ich das nun implementieren?
    Zuerst dachte ich, dieser Funktionsaufruf im Konstuktor auszuführen. Wenn der Aufruf schief geht, blockiert der Konstruktor alle weiteren Interaktionen mit der gesamten Klasse -> Grundlegende Garantie
    Ich wollte aber mindestens eine starke Garantie haben. Mein zweiter Ansatz: Im Konstruktor wir wieder dieser Aufruf getätigt und Ausnahmen können wieder fliegen aber das Objekt ist auch im Falle einer Ausnahme vollständig konstruiert (ausser eben der eine Aufruf der Funktion aus der C-Bibliothek). Die Klasse merkt sich den Nicht-Aufruf der Funktion. Vor jeglicher Interaktion mit der Klasse wird der Status geprüft (also ob die Funktion bereits fehlerfrei ausgeführt worden ist). Falls dies nicht der Fall sein sollte, wird dieser Vorgang (Aufruf der Funktion der C-Bibliothek) wiederholt. Wenn der Aufruf wieder nicht korrekt getätigt worden ist, fliegt wieder eine Exception und an der Klasse verändert sich nichts -> Starke Garantie

    Meine Frage: Ist mein letzter Vorschlag akzeptabel? Verbesserungsvorschläge?

    MfG, EOutOfResources

    EDIT: Für die, die sich nicht mehr erinnern können:
    Grundlegende Garantie = Definierter Status (kein UB)
    Starke Garantie = Gleicher Status (Programm ändert sich nicht)



  • Wieso sollte eine Exception im Ctor keine starke Garantie sein?



  • Im Zusammenhang mit Konstruktoren gibt es keine basic/strong guarantee.

    Konstruktoren sind per Definition immer "strong" (all or nothing), da es das zu konstruierende Objekt ja vor dem Aufruf nicht gibt.

    Die Frage ist, welche anderen Garantien deine Klasse abgibt. Und da ist es nicht unüblich, zu garantieren, dass ein konstruiertes Objekt auch verwendet werden kann. Und das ist nur mit der ersten Variante möglich, die du komischerweise als die schwächere ansiehst.



  • hustbaer schrieb:

    Im Zusammenhang mit Konstruktoren gibt es keine basic/strong guarantee.

    OK, das wusste ich nicht.

    hustbaer schrieb:

    Und da ist es nicht unüblich, zu garantieren, dass ein konstruiertes Objekt auch verwendet werden kann. Und das ist nur mit der ersten Variante möglich, die du komischerweise als die schwächere ansiehst.

    Da muss du mich falsch verstanden haben:

    1. Wenns schief geht, kannst du die Instanz der Klasse vergessen (wird blockiert)
    2. Die Klasse versucht sich selbst vor Interaktionen zu "reparieren" (ruft fehlende Funktion auf)

    EDIT: Also wenn die Funktion doch noch gut geht, kann man die Klasse nach dem Schema 2) noch nutzen...



  • EOutOfResources schrieb:

    Zuerst dachte ich, dieser Funktionsaufruf im Konstuktor auszuführen. Wenn der Aufruf schief geht, blockiert der Konstruktor alle weiteren Interaktionen mit der gesamten Klasse -> Grundlegende Garantie

    Kommt drauf an. Ist das Ergebnis der C-Funktion ein must-Have, d.h. macht ein Objekt ohne das Ergebnis keinen Sinn? Dann wirf einfach eine Exception und fertig.

    Mein zweiter Ansatz: Im Konstruktor wir wieder dieser Aufruf getätigt und Ausnahmen können wieder fliegen aber das Objekt ist auch im Falle einer Ausnahme vollständig konstruiert (ausser eben der eine Aufruf der Funktion aus der C-Bibliothek).

    Geht nicht. Du kannst Ausnahmen aus der Konstruktion von Subobjekten nicht dauerhaft fangen. Wenn dein Objekt nicht konstruiert werden kann, kann es nicht konstruiert werden. Punkt.

    Die Klasse merkt sich den Nicht-Aufruf der Funktion. Vor jeglicher Interaktion mit der Klasse wird der Status geprüft (also ob die Funktion bereits fehlerfrei ausgeführt worden ist). Falls dies nicht der Fall sein sollte, wird dieser Vorgang (Aufruf der Funktion der C-Bibliothek) wiederholt. Wenn der Aufruf wieder nicht korrekt getätigt worden ist, fliegt wieder eine Exception und an der Klasse verändert sich nichts -> Starke Garantie

    Wenn du beim ersten Fehlschlag des C-Aufrufs eine Exception wirfst, dann hat das Objekt nie existiert! Du kannst also später nicht "wieder" eine exception werfen, weil es für das Objekt kein später gibt. Wenn bei der Erzeugung des Objektes eine Exception geworfen wird, dann wird der Scope verlassen, in dem das Objekt erzeugt wurde (besser gesagt, in dem versucht wurde, es zu erzeugen). Nach dem Verlassen gibts aber so oder so das Objekt nicht mehr.

    Zum Thema: http://www.gotw.ca/gotw/066.htm



  • EOutOfResources schrieb:

    1. Wenns schief geht, kannst du die Instanz der Klasse vergessen (wird blockiert)
    2. Die Klasse versucht sich selbst vor Interaktionen zu "reparieren" (ruft fehlende Funktion auf)

    Dafür ist der Client Code plötzlich unheimlich Komplex. Du kannst nämlich nicht garantieren dass das Objekt in einem funktionierenden Zustand ist. dh jedesmal wenn ich irgendeine Funktion des Objektes aufrufe kann der Fehler fliegen dass das Objekt nicht initialisiert werden kann. Das ist eine ziemliche Bürde für den Client Code.

    Cooler ist es, wenn das Objekt immer funktioniert und die Initialisierung an einer zentraleren Stelle gemacht wird, wo ich weiß was ich dagegen tun kann. Aber ich will doch nicht an jeder Stelle wo ich das Objekt verwende die ganzen Fehlerfindungsroutinen für die Initialisierung hinpacken.

    Sinn macht deine 2. Variante dann, wenn der Fehler immer auftreten kann weil das Objekt zB Zugriff auf eine nicht persistente Resource braucht (zB Verbindung zu einem Server). Dann muss man natürlich damit rechnen dass die Verbindung jederzeit abbrechen kann. Aber wenn du zu einem Zeitpunkt garantieren kannst, dass das Objekt immer korrekt funktioniert - dann solltest du das auch tun.

    Das ganze hat übrigens nichts mit der NoThrow, Basic, Strong zu tun.
    kurz gesagt:
    Strong heisst, dass du ein Rollback machst wenn eine Exception fliegt.
    Basic heisst, dass du immer in einem konsistenten Zustand bist wenn eine Exception fliegt.
    No Throw heisst, dass die Funktion nie fehlschlagen kann.

    Ein Objekt dass bei jedem Funktionsaufruf eine Exception wirft, kann die Strong Garantie erfüllen.



  • OK, danke. Dann nutze ich eben die erste Variante.


Log in to reply