Klassendesign INI File
-
Guten Tag,
ich wollte mal eure Meinung zu meinem Klassendesign für eine Ini-Datei hören.
Es gibt die Möglichkeit neue Daten hineinzuschreiben und abzurufen.Ich weiß, eigentlich trivial, aber ich bin mir immer unsicher, ob ich fundamentale Dinge falsch mache und wollte deshalb mal andere Meinungen/Vorschläge hören.
class IniFile { public: IniFile(const std::string &filepath); void open(const std::string &filepath); void write(const std::string &key, const std::string &value, const std::string §ion = nullptr); std::string get(const std::string &key, const std::string §ion = nullptr) const; inline const std::error_code& error() const; private: std::error_code err_; std::fstream file_; };
-
Also eine Referenz auf nullptr setzen ist eher meh. Das könnte sogar kompilieren, weil der string Konstruktor ja einen const char* nimmt, aber ob das auch läuft? Nimm lieber nen Pointer oder schreib = "". Und das inline ist auch sinnlos, das macht der Compiler schon.
-
Drei Punkte die mir spontan auffallen:
- Referenzen mit nullptr als Default?
- Wenn du ein open benötigst, warum dann kein close?
- Wozu brauchst du error/error_code?
-
- Referenzen mit nullptr als Default?
Wieso denn nicht? Es geht mir nur darum, dass in einer INI-Datei nicht zwingend Sections vorhanden sein müssen. Deshalb kann ich die Referenz einfach = nullptr setzen und in der jeweiligen Funktion prüfen, ob ich die Section mit einbeziehen muss, oder nicht. Man kann aber natürlich auch einfach = "" setzen, wie cooky schon erwähnt hat.
- Wenn du ein open benötigst, warum dann kein close?
Hast recht.
- Wozu brauchst du error/error_code?
Ich habe eine Klasse mit std::error_category als Basisklasse geschrieben, dadurch habe ich ein sehr schönes Error-Handling ohne Exceptions/Rückgabewerte. Kann man in "C++11 programmieren" von Torsten T. Will nachlesen. Natürlich brauche ich dann das error_code Objekt das man mit der Methode error() abrufen kann und auf etwaige Fehler prüfen kann.
-
Meine Klasse dafür hat die Methoden:
- parse - einlesen
- compose - speichern
- value Abfrage/Setzen Funktionen
-
ini_file_class schrieb:
Deshalb kann ich die Referenz einfach = nullptr setzen
Nein, kannst du nicht. Da wird der const char* Konstruktor von string genommen.
-
ini_file_class schrieb:
- Wozu brauchst du error/error_code?
dadurch habe ich ein sehr schönes Error-Handling ohne Exceptions/Rückgabewerte.
"Sehr schön" und "ohne Exceptions" würde ich in diesem Fall als Widerspruch empfinden.
-
Nein, kannst du nicht. Da wird der const char* Konstruktor von string genommen.
Ah, natürlich. Werde ich dann ändern.
"Sehr schön" und "ohne Exceptions" würde ich in diesem Fall als Widerspruch empfinden.
Ok, jetzt kann man natürlich eine ewige Diskussion über den Sinn/Unsinn von Exceptions anstoßen.
Mir persönlich sind sie einfach ein Dorn im Auge und ich sehe in diesem Anwendungsbeispiel auch keinen Grund sie zu verwenden.
-
Wenn irgendwelche Klassen von mir Files brauchen, lasse ich einen fstream übergeben.
Ich muss mich dann nicht darum kümmern.
-
ini_file_class schrieb:
... und wollte deshalb mal andere Meinungen/Vorschläge hören.
Ich mache einfach mal einen alternativen Vorschlag:
#include <iostream> #include <iomanip> #include <string> #include <fstream> class ProgramOptions { public: ProgramOptions(); // -- Schnittstelle zum Lesen und Schreiben einzelner Werte class Ref { public: template< typename T > Ref& operator=( const T& value ); }; template< typename T > T get( const std::string& valuename ) const; Ref operator[]( const std::string& valuename ); // --- ini-File IO friend std::ostream& operator<<( std::ostream& out, const ProgramOptions& opts ); friend std::istream& operator>>( std::istream& in, ProgramOptions& opts ); }; // -- Beispiel nur für Demozwecke struct Colour { Colour() : r_(0), g_(0), b_(0) {} Colour( unsigned char r, unsigned char g, unsigned char b ) : r_(r), g_(g), b_(b) {} friend std::ostream& operator<<( std::ostream& out, Colour c ) { const char old_fill = out.fill( '0' ); const std::ios_base::fmtflags oldflags = out.flags(); out << std::hex << std::setw(2) << c.r_ << std::setw(2) << c.g_ << std::setw(2) << c.r_; out.flags( oldflags ); out.fill( old_fill ); return out; } unsigned char r_, g_, b_; }; namespace { const Colour rot( 255, 0, 0 ); } int main() { using namespace std; ProgramOptions options; { ifstream ini( "MeinKonfig.ini" ); if( !ini.is_open() ) { cerr << "Fehler beim Oeffnen des Ini-Files" << endl; return -2; } if( !(ini >> options) ) { cerr << "Fehler beim Lesen des Ini-Files" << endl; return -3; } } // Lesen von Werten 'beliebigen' Typs int val1 = options.get< int >( "Wert1" ); double gain = options.get< double >( "Faktor" ); // Schreiben von Werten 'beliebigen' Typs options["Farbe"] = rot; options["SavePath"] = "C:/egal/meinFile.txt"; options["Wert1"] = val1; // ..... { ofstream ini( "MeinKonfig.ini" ); if( !(ini << options) ) { cerr << "Fehler beim Schreiben des Ini-Files" << endl; } } return 0; }
Laden und Speichern der Datei kannst Du natürlich noch in Funktionen fassen. Ich würde aber die dabei notwendige Fehlerbehandlung von dem Zugriff auf die einzelnen Werte trennen. D.h. Laden/Speichern in Funktionen und Werte-Zugriff in der Klasse 'ProgramOptions'.
Einen Teil der Implementierung obigen Codes findest Du hier.
Und ein fortgeschrittenes Beispiel zum Lesen einer Ini-Datei findest Du hier.Gruß
Werner