Warum bietet das std :: filesystem so viele Nicht-Member-Funktionen?

8

Betrachten Sie zum Beispiel file_size . Um die Größe einer Datei zu erhalten, verwenden wir

%Vor%

Es ist nichts falsch daran, wenn es ganz klar wäre, aber man hat uns beigebracht, dass C ++ eine OO-Sprache ist [Ich weiß, es ist ein Multiparadigma, Entschuldigung an unsere Sprachanwälte :-)] das fühlt sich einfach so an .. . Imperativ (schaudern) zu mir, wo ich das Objekt-ish

erwarte %Vor%

stattdessen. Dasselbe gilt für andere Funktionen wie resize_file , remove_file und wahrscheinlich mehr.

Weißt du, warum Boost und folglich std::filesystem diesen imperativen Stil anstelle des objekthaften gewählt haben? Was ist der Vorteil? Boost erwähnt die Regel (ganz unten), aber keine Begründung dafür.

Ich habe über inhärente Probleme wie p s Zustand nach remove_file(p) oder Fehlerflags (Überladungen mit zusätzlichem Argument) nachgedacht, aber keiner dieser Ansätze löst diese weniger elegant als die anderen.

Sie können ein ähnliches Muster mit Iteratoren beobachten, wo wir heutzutage begin(it) anstelle von it.begin() tun können (sollen?), aber hier denke ich, dass das Grundprinzip mehr im Einklang mit dem nicht modifizierenden% sein sollte. co_de% und so.

    
dlw 27.03.2017, 17:57
quelle

3 Antworten

9

Es gibt ein paar gute Antworten, die schon geschrieben wurden, aber sie kommen nicht zum Kern der Sache: Wenn alle anderen Dinge gleich sind, wenn Sie etwas als eine freie, nicht-Freund-Funktion implementieren können, sollten Sie immer.

Warum?

Weil freie, Nicht-Freund-Funktionen keinen privilegierten Zugriff auf den Status haben. Das Testen von Klassen ist viel schwieriger als das Testen von Funktionen, da Sie sich davon überzeugen müssen, dass die Invarianten der Klasse beibehalten werden, unabhängig davon, welche Elementfunktionen aufgerufen werden oder sogar Kombinationen von Elementfunktionen. Je mehr Mitglied / Freund Funktionen Sie haben, desto mehr Arbeit müssen Sie tun.

Kostenlose Funktionen können eigenständig geprüft und getestet werden. Da sie keinen privilegierten Zugriff auf den Klassenstatus haben, können sie möglicherweise keine Klasseninvarianten verletzen.

Ich kenne die Details der Invarianten und den privilegierten Zugriff path nicht, aber offensichtlich konnten sie viele Funktionen als freie Funktionen implementieren, und sie treffen die richtige Wahl und haben dies getan.

>

Scott Meyers brillanter Artikel zu diesem Thema , der " Algorithmus "ob eine Funktion Mitglied werden soll oder nicht.

Hier ist Herb Sutter, der die massive Schnittstelle von std::string beklagt. Warum? Denn ein Großteil der Schnittstelle von string hätte als freie Funktionen implementiert werden können. Es ist möglicherweise ein wenig unhandlicher, gelegentlich zu verwenden, aber es ist einfacher zu testen, Grund zu überdenken, verbessert die Kapselung und Modularität, öffnet Möglichkeiten für Code Wiederverwendung, die vorher nicht da waren, etc ..

    
Nir Friedman 27.03.2017, 19:38
quelle
10

Die Filesystem-Bibliothek hat eine sehr klare Trennung zwischen dem filesystem::path -Typ, der einen abstrakten Pfadnamen darstellt (der nicht einmal der Name einer existierenden Datei ist) und Operationen, die auf das tatsächliche physische Dateisystem zugreifen, d lese + schreibe Daten auf Festplatten.

Sie haben sogar auf die Erklärung dafür hingewiesen:

  

Die Entwurfsregel lautet, dass rein lexikalische Operationen als Klassenpfad-Elementfunktionen bereitgestellt werden, während Operationen, die vom Betriebssystem ausgeführt werden, als freie Funktionen bereitgestellt werden.

Das ist der Grund.

Es ist theoretisch möglich, filesystem::path auf einem System ohne Festplatten zu verwenden. Die Klasse path enthält nur eine Zeichenfolge und ermöglicht die Bearbeitung dieser Zeichenfolge, die Konvertierung zwischen Zeichensätzen und die Verwendung einiger Regeln, die die Struktur von Dateinamen und Pfadnamen auf dem Host-Betriebssystem definieren. Zum Beispiel weiß es, dass Verzeichnisnamen durch / auf POSIX-Systemen und durch \ unter Windows getrennt sind. Das Bearbeiten der Zeichenfolge, die in path enthalten ist, ist eine "lexikalische Operation", weil sie nur eine String-Manipulation ausführt.

Die Nicht-Member-Funktionen, die als "Dateisystem-Operationen" bekannt sind, sind völlig verschieden. Sie arbeiten nicht nur mit einem abstrakten path -Objekt, das nur eine Zeichenkette ist, sie führen die eigentlichen I / O-Operationen aus, die auf das Dateisystem zugreifen ( stat Systemaufrufe, open , readdir usw.) . Diese Operationen verwenden ein path -Argument, das die zu bearbeitenden Dateien oder Verzeichnisse benennt und dann auf die echten Dateien oder Verzeichnisse zugreift. Sie manipulieren nicht nur Zeichenfolgen im Speicher.

Diese Operationen hängen von der API ab, die vom Betriebssystem für den Zugriff auf Dateien bereitgestellt wird, und sie hängen von der Hardware ab, die auf völlig unterschiedliche Weise zu In-Memory-String-Manipulationen führen kann. Die Festplatten sind möglicherweise voll oder werden möglicherweise vor dem Abschluss eines Vorgangs getrennt oder haben Hardwarefehler.

So gesehen, natürlich ist file_size kein Mitglied von path , weil es nichts mit dem Pfad selbst zu tun hat. Der Pfad ist nur eine Darstellung eines Dateinamens, nicht einer tatsächlichen Datei. Die Funktion file_size sucht nach einer physischen Datei mit dem angegebenen Namen und versucht, ihre Größe zu lesen. Das ist keine Eigenschaft der Datei name , sondern eine Eigenschaft einer persistenten Datei im Dateisystem. Etwas, das vollständig getrennt von der Zeichenkette im Speicher existiert, die den Namen einer Datei enthält.

Anders gesagt, ich kann ein path Objekt haben, das kompletten Unsinn enthält, wie filesystem::path p("hgkugkkgkuegakugnkunfkw") und das ist in Ordnung. Ich kann an diesen Pfad anhängen, oder fragen, ob es ein Stammverzeichnis usw. hat. Aber ich kann die Größe einer solchen Datei nicht lesen, wenn sie nicht existiert. Ich kann einen Pfad zu Dateien haben, die existieren, aber ich habe keine Zugriffsberechtigung, wie filesystem::path p("/root/secret_admin_files.txt"); , und das ist auch gut, weil es nur eine Zeichenkette ist. Ich würde nur einen "Erlaubnis verweigert" -Fehler bekommen, wenn ich versuchte, auf etwas an diesem Ort zuzugreifen, indem ich die Dateisystemoperationsfunktionen benutzte.

Weil path member-Funktionen niemals das Dateisystem berühren, können sie niemals aufgrund von Berechtigungen oder nicht vorhandenen Dateien fehlschlagen. Das ist eine nützliche Garantie.

  

Sie können ein ähnliches Muster mit Iteratoren beobachten, wo wir heute (statt) anfangen können (sollen?) statt it.begin (), aber hier denke ich, dass das Grundprinzip mehr im Einklang mit dem Nicht-Sein sein sollte -modifying next (it) und so.

Nein, weil es genauso gut mit Arrays (die keine Member-Funktionen haben können) und Klassen-Typen funktioniert. Wenn Sie wissen, dass das range-like, mit dem Sie es zu tun haben, ein Container und kein Array ist, können Sie x.begin() verwenden, aber wenn Sie generischen Code schreiben und nicht wissen, ob es ein Container oder ein Array ist, funktioniert std::begin(x) in beiden Fällen.

Die Gründe für diese beiden Dinge (das Dateisystem-Design und die Zugriffsfunktionen für Nichtmitgliedsbereiche) sind keine Anti-OO-Präferenzen, sondern eher vernünftige, praktische Gründe. Es wäre ein schlechtes Design gewesen, wenn man sich auf eines von beiden gestützt hätte, weil es sich für manche Leute, die OO mögen, besser anfühlt oder sich besser für Leute fühlt, die OO nicht mögen.

Außerdem gibt es Dinge, die Sie nicht tun können, wenn alles eine Elementfunktion ist:

%Vor%

Aber wenn file_size ein Mitglied von path war:

%Vor%     
Jonathan Wakely 27.03.2017 19:21
quelle
0

Mehrere Gründe (etwas spekulativ, aber ich verfolge den Standardisierungsprozess nicht sehr genau):

  1. Weil es auf boost::filesystem basiert, das so entworfen wurde. Nun könntest du fragen: "Warum ist boost::filesystem so entworfen?", Was eine faire Frage wäre, aber angesichts der Tatsache, dass es so viele Kilometer gesehen hat, wie es ist, wurde es mit dem Standard akzeptiert sehr wenige Änderungen. So waren einige andere Boost Konstrukte (obwohl manchmal einige Änderungen unter der Haube meistens sind).

  2. Ein allgemeines Prinzip beim Entwerfen von Klassen ist: "Wenn eine Funktion keinen Zugriff auf geschützte / private Mitglieder einer Klasse benötigt und stattdessen vorhandene Mitglieder verwenden kann, wird sie auch nicht Mitglied." Nicht jeder schreibt das zu - es scheint, dass die Designer von boost::filesystem tun.

    Siehe eine Diskussion über (und ein Argument dafür) im Zusammenhang mit std::string() , einer "Monolith" -Klasse mit einer Zillion Methoden, von C ++ - Koryphäe Hebert Sutter, in Guru der Woche # 84 .

  3. Es wurde erwartet, dass wir in C ++ 17 bereits eine Uniform Call Syntax haben (siehe Bjarnes Stroustrup hoch lesbar Vorschlag ). Wenn das in den Standard aufgenommen wurde,

    aufrufen %Vor%

    wäre äquivalent zum Aufruf

    %Vor%

    Du hättest wählen können, was immer du willst. Grundsätzlich.

einpoklum 27.03.2017 19:29
quelle