Ich habe kürzlich über RAII in C ++ gefunden und die meisten Beispiele von RAII sprechen über die Sicherheit von Ausnahmen. Wie Sie immer Ressourcen freigeben können, selbst wenn eine Ausnahme ausgelöst würde.
Die Frage, die ich habe, ist, ob RAII es wert ist, wenn Sie keine Ausnahmen aktiviert haben. In unserer Firma arbeiten wir an Embedded-Projekten für Arm und Ausnahmen sind standardmäßig deaktiviert und wir sehen wirklich keine Notwendigkeit für sie.
Danke für alle Antworten!
RAII mit Ausnahmen ist grundsätzlich eine Voraussetzung.
RAII ohne Ausnahmen bedeutet, dass Sie die Zuweisung von Ressourcen mit dem Code verknüpfen können, um die Ressourcen zu entsorgen.
Damit können Sie Funktionen mit mehreren Exit-Punkten haben, das Schreiben von Destruktoren vereinfachen (oft sind Destruktoren in einer RAII-Umgebung leer oder Standard), können Objektzuordnung und Verschieben vereinfachen (wieder einmal oft leer oder Standard mit ausreichender RAII-Arbeit) ).
Ein klassisches Beispiel für eingebettete Umgebungen wäre das Sperren und Entsperren einiger Mutex. Sie möchten sicherstellen, dass Sie einen Mutex nicht sperren und vergessen, ihn zu entsperren. Um dies zu tun, bedeutet Code-Disziplin, dass Sie im Grunde einen Ausgangspunkt von Ihrer Funktion haben müssen, und Sie müssen manchmal Gymnastik betreiben, um sicherzustellen, dass dies geschieht.
Mit RAII erstellen Sie einfach einen RAII-Ressourceninhaber, der die Sperre besitzt. Jetzt können Sie jederzeit zurückkehren, und der Code zum Entsperren der Ressource wird automatisch an der Rückkehrseite eingefügt. Der Code-Fluss wird vereinfacht und Ressourcenlecks sind weit weniger verbreitet.
RAII ist auch eine erstaunliche Dokumentation. Eine Struktur oder Klasse mit einem Foo*
könnte alles bedeuten: Wie und wann Sie mit dieser Ressource umgehen sollen, ist unklar. Eine Struktur oder Klasse mit einem std::unique_ptr<Foo>
besitzt eindeutig diesen Zeiger. Funktionen, die std::unique_ptr<Foo>
übernehmen, übernehmen eindeutig den Besitz des übergebenen Zeigers. Funktionen, die std::unique_ptr<Foo>
zurückgeben, geben Ihnen eindeutig den Besitz dieses Zeigers.
RAII in C ++ ist eine viel breitere Vorstellung. Es ist ein Idiom, mit dem Sie sichereren Code schreiben können. Der Ressourcenerwerb in RAII ist der Ort, an dem Sie etwas beginnen, das später beendet werden muss, z. B. das Öffnen und Schließen einer Datei, das Zuweisen von Speicher und das Aufheben der Zuordnung, das Erfassen und Freigeben einer Sperre. RAII bezieht sich auf wichtige Begriffe wie: Smart Pointer, Thread-Sicherheit (Steuerung von Mutex-Locks in Multi-Thread-Programmen - Ссылка hier ist ein Beispiel für RAII), Interaktion mit Dateien, Objektbesitz (wenn Sie Smart Pointer wie unique_ptr
verwenden, verwenden Sie bereits RAII) und so weiter.
RAII ist also immer wert, unabhängig von Ausnahmen in gutem C ++ Code zu verwenden.
Ich denke tatsächlich, dass es abhängt und vielleicht werde ich hier für die seltsamste Antwort unter meinen C ++ Peers konkurrieren. Das heißt, wenn Sie bereits das Rich-Type-System von C ++ und C ++ verwenden, wäre es wahnsinnig, RAII auch ohne Ausnahmen größtenteils nicht zu verwenden.
Der Mangel an fehlender RAII beim Entwerfen und Verwenden von Schnittstellen
Im Allgemeinen wollen Sie keine icky-Funktionen schreiben, die Ressourcen zurückgeben, die Sie in der Funktion zuweisen, die Clients manuell / extern freigeben / schließen / zerstören müssen. Gleichzeitig kann es zeitaufwändig sein, den Entwurf zu invertieren und dem Client die Ressourcen zuzuweisen und sie nach Parametern zu übergeben, damit eine Funktion verwendet werden kann (einen vom Client zugewiesenen Puffer ausfüllen, z. B. g) und noch mit die Verantwortung des Kunden, freizugeben / zu schließen / zu zerstören (nur ein bisschen sauberer, da der Kunde die Ressource zumindest selbst erstellt / geöffnet / zugeteilt hat). Das Entwerfen einer Funktion, die eine Zeichenfolge variabler Länge zurückgibt, deren Inhalt innerhalb der Funktion bestimmt wird, wenn sie aufgerufen wird, ist zeitraubend und immer ein wenig eklig, um entweder RAII zu verwenden oder zu implementieren oder beides.
Dies sind alltägliche Ärgernisse beim Entwerfen und Verwenden von Schnittstellen in C, mit denen Sie in C ++ nicht zu tun haben müssen, wenn Sie über Ressourcen verfügen, die nach sich selbst bereinigen, wenn sie den Gültigkeitsbereich verlassen. Die Zeit, die es braucht, um nur ein paar Konstruktoren und Destruktoren zu geben, überwiegt oft das zusätzliche Ärgernis, sich mit dem oben Gesagten auseinandersetzen zu müssen.
Gefahren ohne RAII
Und ähnliche Dinge für einfache Dinge wie einen Mux mit Zielfernrohr. Es schützt Sie definitiv vor einigen möglichen zukünftigen Fehlern, damit ein Mutex sich selbst explizit entsperrt, wenn es den Gültigkeitsbereich verlässt. Es kann leicht genug sein, Bugs in solchen Fällen persönlich zu vermeiden, ohne Ausnahmen beim ersten Schreiben des Codes zu machen und es zu testen, aber ein Kollege könnte in der Zukunft hoffentlich einige frühe return
-Anweisungen während der Crunch-Zeit in diese Funktion einführen und vergessen zu unlock
der Mutex, wenn das explizit benötigt wird. Wie auch immer es wahrscheinlich oder unwahrscheinlich ist, es ist schön, den Mutex im Umkreis zu haben.
Der Vorteil eines dummen Systemtyps
Aber ich bin ein seltsamer Typ, der sowohl C als auch C ++ liebt und zwischen beiden hin und her springt, und es gibt eine Sache, die ich viel einfacher in C zu tun habe und die Low-Level-Datenstrukturen implementiert, die nur funktionieren können über jeden Datentyp. Das liegt daran, dass Sie in C kein Rich-Type-System mit Objekten haben, die vtables und dtors und ctors haben können und Sie keine Ausnahmen haben. Sie können Datentypen im Allgemeinen als nur Bits und Bytes zu memcpy
hier und memmove
dort, malloc
Speicher für alles hier und realloc
dort behandeln. Und der Grund, warum wir das souverän in C machen können, ist, dass das Typsystem so dumm ist und all diese Funktionen nicht besitzt.
Natürlich sind die Datenstrukturen, die ich in C entwerfe, nicht bequem zu benutzen. Sie haben keine Typsicherheit und arbeiten oft mit void*
Zeigern, sie müssen manuell zerstört werden, usw. Sie sind einfach zu implementieren und cachefreundlich, während sie die Heapzuweisungen minimieren, da sie mich wirklich auf Speicherlayouts konzentrieren Darstellungen, wenn ich Datentypen nur als Bits und Bytes betrachten kann, die gemischt werden sollen. In der Zwischenzeit ist es in C ++ nicht einfach, einen growbaren Array-Container wie std::vector
korrekt zu implementieren, während Exception-Sicherheit und placement new
und manuelle Aufrufe von Destruktoren usw. während der Zuordnung und Freigabe durch std::allocator
bereitgestellt werden. % Co_de% ist jedoch viel bequemer und sicherer zu verwenden als jede Datenstruktur, die ich in C entworfen habe.
Es gibt also eine besondere Art von Bequemlichkeit für ein Typsystem, das keine Destruktoren und Konstruktoren usw. hat, und C wäre nicht unbedingt besser für das, was es hervorbringt, wenn es Destruktoren und Konstruktoren erhält, um RAII-konforme Ressourcen zuzulassen , da plötzlich alle Arten der vorhandenen täglichen C-Funktionen wie std::vector
nicht mehr vernünftig für die Verwendung wären. Sie würden plötzlich die tödlichsten, fehleranfälligsten Funktionen unter solch einem Typsystem werden, wie sie in C ++ sind.
Daher denke ich, dass man argumentieren muss, dass RAII in einigen Low-Level-Domains tatsächlich ein Hindernis sein könnte, allerdings nur für diese sehr niedrigen Domains. Inzwischen ist es sehr vorteilhaft für alles andere, mit oder ohne Ausnahmen. Ausnahmen machen RAII fast zu einer Anforderung, aber abgesehen von diesen sehr niedrigen Domänen sind sie trotzdem äußerst nützlich.
Trivial Constructible / Destructible UDTs
Manchmal möchte ich, dass das Schlüsselwort memcpy
in C ++ eine Struktur auf einen einfachen alten Datentyp reduziert, der trivial konstruierbar und zerstörbar ist (die Art von Dingen, mit der wir sicher umgehen können), mit Compiler-Sicherheitsvorkehrungen, die das verhindern es von Speichern von Datenelementen, die nicht sind.Ich würde weniger Gründe für die Verwendung von C finden, wenn dies der Fall ist, da wir dann generische Container schreiben könnten, die nur mit solchen struct
und nicht memcpy
arbeiten können, ohne sich auf Typeigenschaften zu verlassen, um festzustellen, ob ein generischer Typ trivial ist konstruierbar / zerstörbar. Denn die Anziehungskraft auf C war nie so sehr auf das Fehlen von RAII zurückzuführen, als dass man leicht annehmen könnte, dass viele Datentypen, die wir in unseren Datenstrukturen speichern und mit denen wir täglich arbeiten, es nicht brauchen. Es ist nett, wenn Sie Datenstrukturen implementieren, um zu wissen, dass Sie structs
einen zusammenhängenden Speicherblock für classes
-Elemente bilden können, ohne sie durchlaufen und Destruktoren aufrufen zu müssen. In C ++ muss man sich oft auf der sicheren Seite irren und davon ausgehen, dass so gut wie alles es braucht (oder es in der Zukunft brauchen wird) bis zu dem Punkt, wo, sagen wir free
, stark davon abgehalten wird irgendwo von irgendwem benutzt zu werden .