Ressourcenhandles - Standardkonstruktoren verbieten?

8

Ich habe also eine Bibliotheksentwicklung gemacht und bin in ein Dilemma geraten. Die Bibliothek ist privat, daher kann ich sie nicht teilen, aber ich denke, das könnte eine sinnvolle Frage sein.

Das Dilemma stellte sich als ein Problem dar, warum es keinen Standardkonstruktor für eine Ressourcenbehandlungsklasse in der Bibliothek gibt. Die Klasse behandelt eine bestimmte Dateistruktur, die nicht wirklich wichtig ist, aber wir können die Klasse Quake3File aufrufen.

Die Anforderung bestand dann darin, einen Standardkonstruktor und die "geeigneten" Open / Close-Methoden zu implementieren. Meine Denkrichtung ist RAII-Stil, das heißt, wenn Sie eine Instanziierung der Klasse erstellen, müssen Sie ihr eine Ressource zuweisen, die sie behandelt. Dadurch wird sichergestellt, dass alle erfolgreich erstellten Handles gültig sind und IMO eine ganze Klasse von Fehlern beseitigt.

Mein Vorschlag war, einen (intelligenten) Zeiger herumzuhalten und dann, anstatt ein Open / Close zu implementieren und eine Dose Würmer zu öffnen, erstellt der Benutzer die Klasse im freien Speicher, um sie zu "öffnen" und zu löschen Sie möchten es "schließen". Mit einem intelligenten Zeiger wird es sogar für Sie "geschlossen", wenn es den Bereich verlässt.

Hier kommt der Konflikt ins Spiel. Ich mag es, das STL-Design von Klassen nachzuahmen, da dies meine Klassen einfacher zu benutzen macht. Da ich eine Klasse mache, die sich hauptsächlich mit Dateien beschäftigt, und wenn ich std :: fstream als Richtlinie nehme, bin ich mir nicht sicher, ob ich einen Standardkonstruktor implementieren soll. Die Tatsache, dass die gesamte std :: fstream Hierarchie dies tut, weist mich auf ein Ja hin, aber mein eigenes Denken geht zu Nein.

Also sind die Fragen mehr oder weniger:

  1. Sollten Ressourcenhandles wirklich Standardkonstruktoren haben?
    • Was ist ein guter Weg, um einen Standardkonstruktor für eine Klasse zu implementieren, die mit Dateien arbeitet? Setzen Sie den internen Status auf einen ungültigen Wert und wenn ein Benutzer ihn missbraucht, indem er ihm keine Ressource gibt, führt dies zu undefiniertem Verhalten? Es scheint seltsam zu sein, diesen Weg zu nehmen.
  2. Warum implementiert die STL die fstream-Hierarchie mit Standardkonstruktoren? Legacy Gründe?

Hoffen Sie, dass meine Frage verstanden wird. Danke.

    
Honf 04.12.2013, 19:19
quelle

3 Antworten

5

Man könnte sagen, dass es zwei Kategorien von RAII-Klassen gibt: "immer gültig" und "vielleicht leer". Die meisten Klassen in Standardbibliotheken (oder Fast-Standard-Bibliotheken wie Boost) gehören der letzteren Kategorie an, aus verschiedenen Gründen, die ich hier erklären werde. Mit "immer gültig" meine ich Klassen, die in einen gültigen Zustand gebracht werden müssen und dann bis zur Zerstörung gültig bleiben. Und mit "vielleicht leer" meine ich Klassen, die in einem ungültigen (oder leeren) Zustand konstruiert oder irgendwann ungültig (oder leer) werden könnten. In beiden Fällen bleiben die RAII-Prinzipien bestehen, d. H. Die Klasse handhabt die Ressource und implementiert eine automatische Verwaltung derselben, so wie sie die Ressource bei Zerstörung freigibt. Aus der Sicht eines Anwenders genießen beide den gleichen Schutz gegen undichte Ressourcen. Aber es gibt einige wichtige Unterschiede.

Als erstes ist zu berücksichtigen, dass ein wichtiger Aspekt von fast jeder Ressource, die ich mir vorstellen kann, darin besteht, dass Ressourcenbeschaffung immer scheitern kann. Beispielsweise könnten Sie eine Datei nicht öffnen, Speicher nicht zuordnen, eine Verbindung nicht herstellen, einen Kontext für die Ressource nicht erstellen usw. Sie benötigen eine Methode, um diesen potenziellen Fehler zu beheben. In einer "immer gültigen" RAII-Klasse haben Sie keine andere Wahl, als diesen Fehler zu melden, indem Sie eine Ausnahme vom Konstruktor auslösen. In einer "vielleicht leeren" -Klasse können Sie angeben, dass der Fehler entweder dadurch gemeldet wird, dass das Objekt leer bleibt, oder Sie eine Ausnahme auslösen können. Dies ist wahrscheinlich einer der Hauptgründe, warum die IO-Streams-Bibliothek dieses Muster verwendet, weil sie sich dafür entschieden haben, eine optionale Funktion in ihren Klassen zu verwenden (wahrscheinlich aufgrund der Zurückhaltung vieler Leute, Ausnahmen zu sehr zu verwenden).

Zweitens ist zu beachten, dass "immer gültige" Klassen keine beweglichen Klassen sein können. Beim Verschieben einer Ressource von einem Objekt zu einem anderen wird das Quellobjekt "leer" gemacht. Dies bedeutet, dass eine "immer gültige" Klasse nicht kopierbar und unbeweglich sein muss, was für die Benutzer ein gewisses Ärgernis darstellen könnte und auch Ihre eigene Fähigkeit einschränken könnte, eine einfach zu bedienende Benutzeroberfläche bereitzustellen ( zB Fabrikfunktionen, usw.). Dies erfordert auch, dass der Benutzer das Objekt auf Freestore zuweist, wann immer er das Objekt bewegen muss.

(BEARBEITEN) Wie von DyP unten ausgeführt, könnten Sie eine "immer gültige" Klasse haben, die beweglich ist, solange Sie das Objekt in einen zerstörbaren Zustand versetzen können. Mit anderen Worten, jede andere nachfolgende Verwendung des Objekts wäre UB, nur die Zerstörung würde sich gut verhalten. Es bleibt jedoch, dass eine Klasse, die eine "immer gültige" Ressource erzwingt, weniger flexibel ist und dem Benutzer etwas Ärger bereitet. (ENDE BEARBEITEN)

Offensichtlich ist eine "immer gültige" Klasse, wie Sie bereits sagten, im Allgemeinen in ihrer Implementierung narrensicherer, da Sie den Fall, in dem die Ressource leer ist, nicht berücksichtigen müssen. Mit anderen Worten, wenn Sie eine "vielleicht leere" Klasse implementieren, müssen Sie innerhalb jeder Mitgliedsfunktion prüfen, ob die Ressource gültig ist (z. B. ob die Datei geöffnet ist). Aber denken Sie daran, dass die "Einfachheit der Implementierung" kein gültiger Grund dafür ist, eine bestimmte Schnittstellenauswahl zu diktieren, da die Schnittstelle dem Benutzer gegenübersteht. Aber dieses Problem gilt auch für Code auf der Benutzerseite. Wenn der Benutzer ein "vielleicht leeres" Objekt behandelt, muss er immer die Gültigkeit überprüfen, und das kann problematisch und fehleranfällig werden.

Andererseits muss sich eine "immer gültige" Klasse ausschließlich auf Ausnahmemechanismen verlassen, um ihre Fehler zu melden (dh Fehlerbedingungen verschwinden nicht wegen des Postulats von "immer gültig") und können somit vorliegen einige interessante Herausforderungen bei der Umsetzung. Im Allgemeinen müssen Sie strenge Sicherheitsgarantien für alle Funktionen dieser Klasse haben, einschließlich Implementierungscode und Benutzercode. Wenn Sie beispielsweise postulieren, dass das Objekt "immer gültig" ist und Sie einen fehlgeschlagenen Vorgang versuchen (z. B. das Lesen über das Ende einer Datei hinaus), müssen Sie diese Operation rückgängig machen und das Objekt auf seine ursprüngliche Gültigkeit zurücksetzen Stellen Sie Ihr "immer gültiges" Postulat durch. Der Benutzer wird im Allgemeinen gezwungen sein, das gleiche zu tun, wenn es relevant ist. Dies ist möglicherweise mit der Art der Ressource, mit der Sie es zu tun haben, nicht kompatibel.

(BEARBEITEN) Wie von DyP unten erwähnt, gibt es Graustufen zwischen diesen beiden Arten von RAII-Klassen. Bitte beachten Sie, dass diese Erklärung zwei Pole-Gegensätze oder zwei allgemeine Klassifikationen beschreibt. Ich sage nicht, dass dies eine Schwarz-Weiß-Unterscheidung ist. Offensichtlich haben viele Ressourcen unterschiedliche Grade an "Gültigkeit" (z. B. könnte ein ungültiger Datei-Handler sich in einem Zustand "nicht geöffnet" oder einem Zustand "erreichtes Dateiende" befinden, der anders gehandhabt werden könnte, dh wie a "immer geöffnet", "vielleicht bei EOF", Datei-Handler-Klasse). (ENDE BEARBEITEN)

  

Sollten Resource Handles wirklich Standardkonstruktoren haben?

Standardkonstruktoren für RAII-Klassen werden im Allgemeinen so verstanden, dass sie das Objekt in einen "leeren" Zustand bringen, was bedeutet, dass sie nur für "vielleicht leere" Implementierungen gültig sind.

  

Was ist eine gute Methode, um einen Standardkonstruktor für eine Klasse zu implementieren, die mit Dateien arbeitet? Setzen Sie den internen Status auf einen ungültigen Wert und wenn ein Benutzer ihn missbraucht, indem er ihm keine Ressource gibt, führt dies zu undefiniertem Verhalten? Es scheint seltsam zu sein, diesen Weg zu nehmen.

Die meisten Ressourcen, auf die ich jemals gestoßen bin, haben eine natürliche Möglichkeit, "Leere" oder "Ungültigkeit" auszudrücken, sei es ein Nullzeiger, Null-Datei-Handle oder nur ein Flag, um den Status als gültig zu kennzeichnen. Also, das ist einfach. Dies bedeutet jedoch nicht, dass ein Missbrauch der Klasse "undefiniertes Verhalten" auslösen sollte. Es wäre absolut schrecklich, eine solche Klasse zu entwerfen. Wie ich bereits sagte, gibt es Fehlerbedingungen, die auftreten können, und die Klasse "immer gültig" ändert diese Tatsache nicht, nur die Mittel, mit denen Sie mit ihnen umgehen. In beiden Fällen müssen Sie nach Fehlerbedingungen suchen und diese melden und das Verhalten Ihrer Klasse für den Fall angeben, dass sie auftreten. Sie können nicht einfach sagen: "Wenn etwas schief geht, der Code hat 'undefiniertes Verhalten'", müssen Sie das Verhalten Ihrer Klasse (auf die eine oder andere Weise) im Falle von Fehlerbedingungen angeben, Punkt.

  

Warum implementiert die STL die fstream-Hierarchie mit Standardkonstruktoren? Legacy Gründe?

Erstens ist die IO-Stream-Bibliothek nicht Teil der STL (Standard Template Library), aber das ist ein häufiger Fehler. Wie auch immer, wenn Sie meine obigen Erklärungen lesen, werden Sie wahrscheinlich verstehen, warum die IO-Stream-Bibliothek sich dafür entschieden hat, die Dinge so zu machen, wie sie es tun. Ich denke, dass es im Wesentlichen darauf hinausläuft, Ausnahmen als notwendigen grundlegenden Mechanismus für ihre Implementierung zu vermeiden. Sie erlauben Ausnahmen als Option, machen sie aber nicht zwingend, und ich denke, dass das für viele Menschen eine Notwendigkeit gewesen sein muss, besonders in den Tagen, als es geschrieben wurde, und wahrscheinlich auch heute noch.

    
Mikael Persson 04.12.2013, 20:48
quelle
4

Ich denke, dass jeder Fall separat betrachtet werden sollte, aber für eine Dateiklasse würde ich sicherlich die Einführung eines "ungültigen Status" wie "Datei kann nicht geöffnet werden" (oder "keine Datei an die Wrapper-Handler-Klasse angehängt ").

Wenn Sie beispielsweise den Status "ungültige Datei" nicht haben, erzwingen Sie, dass eine Methode zum Laden einer Datei oder eine Funktion Ausnahmen auslöst, falls eine Datei nicht geöffnet werden kann. Ich mag das nicht, weil der Aufrufer dann viele try/catch -Wrapper um den Dateiladecode verwenden sollte, während stattdessen eine gute alte boolesche Prüfung gut wäre.

%Vor%

Ich bevorzuge diesen Stil:

%Vor%

Darüber hinaus kann es auch sinnvoll sein, die File class beweglich zu machen (also können Sie auch File Instanzen in Container setzen, zB std::vector<File> ), und in diesem Fall Sie müssen einen "ungültigen" Status für eine verschobene Dateiinstanz haben.

Also, für eine Klasse File würde ich die Einführung eines ungültigen Zustandes für gut halten.

Ich habe auch einen RAII-Template-Wrapper in rohe Ressourcen geschrieben, und ich habe dort auch einen ungültigen Zustand implementiert. Dies wiederum ermöglicht die korrekte Implementierung der Bewegungssemantik.

    
Mr.C64 04.12.2013 19:50
quelle
1

Zumindest IMO, Ihr Denken zu diesem Thema ist wahrscheinlich besser als das in Iostreams gezeigt. Persönlich, wenn ich heute ein Analog von iostreams von Grund auf neu erstellen würde, hätte wahrscheinlich keinen Standard-ctor und separate open . Wenn ich einen fstream benutze, übergebe ich fast immer den Dateinamen an den ctor, anstatt den Standard-Konstruktor, gefolgt von open .

Fast der einzige Punkt, der dafür spricht, einen Standard-Ctor für eine Klasse wie diesen zu haben, ist, dass sie es einfacher macht, sie in eine Sammlung zu setzen. Mit der Bewegungssemantik und der Fähigkeit, Objekte zu platzieren, wird dies jedoch weniger zwingend. Es war nie wirklich notwendig und ist jetzt fast irrelevant.

    
Jerry Coffin 04.12.2013 19:44
quelle

Tags und Links