010
16.12.2005, 21:42 Uhr
Spacelord
Hoffnungsloser Fall
|
Hi, wie so oft bei C++ gibt es eine schnelle Lösung und eine,die zwar erstmal etwas aufwendiger ist,die einem aber langfristig das Leben erleichtern kann. Imho sind mit C++ istringstreams das Mittel der Wahl um Daten aus Strings in andere Datentypen zu "wandeln".Generell kann ein istringstream jeden Datentyp verarbeiten der eine sinnvolle Implementierung des >>Operators bietet. Die folgende Lösung bietet,zur besseren Übersicht, nur die Basisfunktionalität ohne sich gross mit Fehlerfällen zu beschäftigen. Als erstes mal ne abstrakte Basisklasse die ich später für ne std::map als Datentyp benötige um verschiedene Template Subtypen unter eine Schnittstelle zu bekommen.
C++: |
#ifndef __CONFIG_ELEMENT_H #define __CONFIG_ELEMENT_H
#include <string>
class config_element { public: virtual void put(const std::string& s)const=0; virtual ~config_element() {} }; #endif
|
Dann die Template Subklasse:
C++: |
#ifndef __CONFIG_ELEMENT_DATA_H #define __CONFIG_ELEMENT_DATA_H
#include <sstream> #include <string> #include "config_element.h"
template<typename T> class config_element_data : public config_element { public: config_element_data(T& item) : ref_(item){} virtual void put(const std::string& s)const { std::istringstream stream(s); stream >> ref_; } private: T& ref_; };
#endif
|
Die Methode put erledigt die eigentliche Arbeit und versucht aus dem istringstream die Daten zu extrahieren.Das ganze richtet sich nach dem Typen der als Templateargument übergeben wurde und setzt voraus dass dieser Typ den >>Operator anbietet.
Als nächstes dann noch die Basisklasse für die Konfiguration:
C++: |
#ifndef __CONFIGURATION_BASE_H #define __CONFIGURATION_BASE_H
#include <fstream> #include <string> #include <map> #include <sstream> #include "config_element.h"
class configuration_base { typedef std::map<std::string,config_element* > config_map_t; public: configuration_base(){} virtual ~configuration_base() { config_map_t::iterator it = config_map.begin(); while(it != config_map.end()) { delete((*it).second); it++; } }
bool setValue(const std::string &s) { std::istringstream isstr(s); std::string valueName; std::string value; isstr>>valueName; isstr>>value; config_map_t::iterator it; it = config_map.find(valueName); if(it!=config_map.end()) { (*it).second->put(value); return true; } else return false; } void loadConfiguration(const char* filename) { std::ifstream ifstr(filename); if(ifstr) { std::string buf; while(std::getline(ifstr,buf)) setValue(buf); ifstr.close(); } } protected: config_map_t config_map; private: virtual void init() = 0; };
#endif
|
Interessant ist hier das "Herz" der Klasse.
C++: |
typedef std::map<std::string,config_element* > config_map_t;
|
Die map besteht aus nem String als Schlüssel, der den Namen einer möglichen Einstellung enthält,und als zweiten Parameter einen config_element Zeiger der letztendlich aber auf eine konkrete Instanz von config_element_data<Datentyp> zeigt. Weiter interessant ist die rein virtuelle Methode init die von einer Subklasse überschrieben werden muss um für eine spezielle Anwendung die map mit den möglichen Einstellungen zu initialisieren.Der Rest erledigt halt das übliche Gelumpe wie laden,Wert setzen etc. Das ist dann soweit der wiederverwendbare Teil. Benutzt wird der Kram dann in der Art dass von configuration_base ne Klasse abgeleitet wird,die dann init implementiert,die möglichen Einstellungen vorgibt(alles andere wird ignoriert)und in welchen Datentyp der String für welche Einstellung konvertiert werden muss.
Also als Beispiel sowas(wobei hier die Variablen einfach public sind,aber das kann ja jeder dann halten wie er will)
C++: |
#ifndef __DEMO_CONFIG_H #define __DEMO_CONFIG_H
#include "config_element_data.h" #include "configuration_base.h"
class demo_configuration : public configuration_base { public: int wert1; double wert2; std::string str; demo_configuration() { wert1=0; wert2=0.0; str="Default"; init(); } private: void init() { config_map.insert(std::make_pair("Wert1",new config_element_data<int>(wert1))); config_map.insert(std::make_pair("Wert2",new config_element_data<double>(wert2))); config_map.insert(std::make_pair("Name",new config_element_data<std::string>(str))); } };
#endif
|
Und zum Testen noch ne main:
C++: |
#include <iostream> #include "demo_configuration.h"
int main() { //Testfile erstellen std::ofstream out("conf.txt"); if(!out) return -1; out<<"Wert1"<<'\t'<<12<<'\n'; out<<"Wert2"<<'\t'<<3.14<<'\n'; out<<"Name"<<'\t'<<"Teststring"<<'\n'; out.close();
demo_configuration config;
std::cout<<"Vor dem laden:"<<std::endl; std::cout<<config.str<<std::endl; std::cout<<config.wert1<<std::endl; std::cout<<config.wert2<<std::endl;
config.loadConfiguration("conf.txt");
std::cout<<"\nNach dem laden:"<<std::endl; std::cout<<config.str<<std::endl; std::cout<<config.wert1<<std::endl; std::cout<<config.wert2<<std::endl;
return 0; }
|
Ist sicherlich auch noch nicht der Stein der Weisen aber in etwa in diese Richtung würde ich von einer sauberen,wiederverwendbaren C++ Lösung sprechen.
MfG Spacelord -- .....Ich mach jetzt nämlich mein Jodeldiplom.Dann hab ich endlich was Eigenes. |