Jump to content
  • Advertisement
Sign in to follow this  
cptrnet

loading config file into map

This topic is 4828 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Hi, I am trying to load properties from a config file into a map. What I want to do is have different properties for my application. The thing I don't know how to do is have a template datatype for the value. For example the title of the window is a string and the width and height is a number. I don't want a type casting nightmare. I declare my map as of now like this: std::map<std::string, std::string> properties; Then I just load in the key-value pair from the text file in this format: Title Tetris Width 800 Height 600 etc. Could I do something like this: std::map<std::string, TYPE "type whatever"> properties; Is this possible with templates? Or do I need to figure out some other idea?

Share this post


Link to post
Share on other sites
Advertisement
Quote:
Original post by cptrnet
Is this possible with templates?


Not directly no.

Quote:
Original post by cptrnet
Or do I need to figure out some other idea?


Automatic type erasor is one technic but i'd check this first though

Share this post


Link to post
Share on other sites
This is how I achieve this in my engine "iltuomondofuturo":

#ifndef _CONFIGURATION_H
#define _CONFIGURATION_H

#include <sstream>

namespace iltuomondofuturo
{

class ConfigVarBase
{
protected:

const char* Name;
bool Changed;

explicit ConfigVarBase(const char* Name) :
Name(Name), Changed(false) { }

public:

virtual void StringToValue(const std::string& Value) = 0;
virtual std::string ValueToString() = 0;

const char* GetName() { return Name; }
bool IsChanged() { return Changed; }
};

template<typename T>
class ConfigVar : public ConfigVarBase
{
public:

explicit ConfigVar(const char* Name):
ConfigValueBase(Name) { }

explicit ConfigVar(const char* Name, T Val) :
ConfigValueBase(Name), Value(Val) { }

explicit ConfigVar(const char* Name, const std::string& Value) :
ConfigVarBase(Name) { StringToValue(Value); }

void SetValue(T Val) { Value = Val; }
T GetValue() { return Value; }

void StringToValue(const std::string& Val)
{
std::istringstream str(Value);
str.unsetf(std::ios::skipws);
str.setf(std::ios_base::boolalpha);
str >> Value;
}

std::string ValueToString()
{
std::ostringstream str;
str.unsetf(std::ios::skipws);
str.setf(std::ios_base::boolalpha);
str << Value;
return str.str();
}

ConfigVar<T>& operator = (const T& val)
{
Changed = true;
Value = val;
return *this;
}

operator const T() { return Value; }

private :
T Value;
};

template<>
class ConfigVar<std::string> : public ConfigVarBase
{
public:

explicit ConfigVar<std::string>(const char* Name, const std::string& Val):
ConfigVarBase(Name), Value(Val) { }

explicit ConfigVar<std::string>(const char* Name):
ConfigVarBase(Name) { }

void StringToValue(const std::string& Val) { Value = Val; }
std::string ValueToString() { return Value;}

ConfigVar<std::string>& operator = (const std::string& val)
{
Changed = true;
Value = val;
return *this;
}

operator const std::string() { return (const std::string)Value; }

std::string Value;
};

template<typename Type, unsigned int Number>
class ConfigVarArray : public ConfigVarBase
{
public:

explicit ConfigVarArray(const char* Name):
ConfigValueBase(Name) { }

explicit ConfigVarArray(const char* Name, const std::string& Value):
ConfigVarBase(Name)
{ StringToValue(Value); }

bool StringToValue(const std::string& Val)
{
unsigned int i = 0;
std::istringstream str(Val);
str.setf(std::ios_base::boolalpha);
while((str >> Value) && (i++ < Number)) { }
return str ? true : false;
}

std::string ValueToString()
{
std::ostringstream str;
str.unsetf(std::ios::skipws);
str.setf(std::ios_base::boolalpha);
for(unsigned int i = 0; i < Number; i++)
str << (Type)Value << ' ';
return str.str();
}

Type& operator[](unsigned int Pos) { return Value[Pos]; }
unsigned int Size() { return Number; }
Type Value[Number];
};

typedef ConfigVar<int> ConfigInt;
typedef ConfigVar<unsigned int> ConfigUnsignedInt;
typedef ConfigVar<float> ConfigFloat;
typedef ConfigVar<bool> ConfigBool;
typedef ConfigVar<std::string> ConfigString;

class Configuration
{
public:

virtual void GetValue(const std::string& Section, ConfigVarBase* Cfg) const = 0;
virtual void SetValue(const std::string& Section, ConfigVarBase* Cfg) = 0;
};

}

#endif


it's a little tricky. I use:
operator const T() { return Value; }
and a template specialization for std::string
and a special ConfigArray for multiple values like:
SomeGoodCVar = foo bar quax etc

I load the values in another class cause in this way I can support xml, ini or whatever else grammar. If you want also that code I can post here.
Hope it helps.

Share this post


Link to post
Share on other sites
Whoa thats looks about as big of pain as type casting.

bjogio thanks for the reply, if you want to post the code that would be cool.

Share this post


Link to post
Share on other sites
Quote:
Original post by bjogio
This is how I achieve this in my engine "iltuomondofuturo":....


That code has some "issues", like SetValue passing arguments by value and GetValue returning by value. It also uses manual type erasor as opposed to automatic type erasor which is much nicer (like Boost.Any does).

Share this post


Link to post
Share on other sites
What?

ConfigUnsignedInt foo("foo", 0);
ConfigBool bar("foo", false);

std::cout << foo << bar;
foo = 3;
bar = false;
std::cout << foo << bar;

where is typecast?
Example of loading windows standard *.ini


//Load the configuration
void ConfigurationSystem::Load(const char* FileName)
{
GUARD

std::string file(FileName);
if(UserDef == false)
{
if(strcmp(FileName, UserFileName.c_str()) == 0) //HACK "ugly"
{
UserDef = true;
return;
}
}

std::ifstream inifile(FileName, std::ios::in);
if(!inifile) EXCEPT( Exception::ExceptionInternal, "Problems occurred in" + file);

std::string Line, Section, Key, Value;

while (std::getline(inifile,Line))
{
//Format(Line);
if(Line != "")
{
if(Line.at(0) == '[' && Line[Line.length()-1] == ']')
{
Section = Line;
Section = Line.substr(1, Line.length()-2);

if(Section.size() == 0)
EXCEPT( Exception::ExceptionInternal, file + " : ( " + Line +
" ) Section not specified");
} else
{
size_t x;
x = Line.find("=");

if(x == std::string::npos)
EXCEPT( Exception::ExceptionInternal, file + " : ( " + Line +
" ) Key not specified");

Key = Line.substr(0, x);
Value = Line.substr(x + 1, Line.length());

if(Key.length() == 0 || Value.length() == 0)
EXCEPT( Exception::ExceptionInternal, file + " : ( " + Line +
" ) Key not specified");

AddValue(Section, Key, Value);
}
}
}

inifile.close();
++NumFile;

UNGUARD(ClassName, "Load");
}


My class ConfigurationSystem can hold or:

typedef std::map<std::string, std::string> KeyMap;
KeyMap UserSettings;

or:

typedef std::map<std::string, ConfigValueBase*> KeyMap;
KeyMap UserSettings;

but I prefer the first and getting data in this way:


void ConfigurationSystem::GetValue(const std::string& Section, ConfigValueBase* Cfg)
{
Cfg->StringToValue(GetValue(Section, Cfg->GetName()));
++NumAccess;
}

std::string ConfigurationSystem::GetValue(const std::string& Section, const std::string& Key)
{
GUARD

bool found = false;

SectionMap* ToWatch = &UserSettings;

SectionIterator = ToWatch->find(Section);
if(SectionIterator != (*ToWatch).end())
{
KeyIterator = (*SectionIterator).second.find(Key);
found = (KeyIterator != (*SectionIterator).second.end());
}

if(!found)
{
ToWatch = &ConstSettings;
SectionIterator = ToWatch->find(Section);
if(SectionIterator != (*ToWatch).end())
{
KeyIterator = (*SectionIterator).second.find(Key);
found = (KeyIterator != (*SectionIterator).second.end());
}
}

if(!found)
{
std::string str("Key: " + Key + " Section: " + Section);
EXCEPT(Exception::ExceptionInternal, str);
}

return KeyIterator->second;

UNGUARD(ClassName, "GetValue")
}



cause in this way I separate the implementation of CVar for the Configuration pool. Bye.

Share this post


Link to post
Share on other sites
I use boost::any for my config system. Works fine.

The way mines works, is I have a base class for a configuration variable, that can hold any type (internally it uses boost::any, to store the value). Then I have classes that inherit from the base type, to implement different types.

If I want to access a configuration variable, I simply declare a configuration variable with the appropriate name, which automatically uses the config system to look up the right variable.

e.g.


Core::ConsoleBool dbg_disablemouse ( "dbg_disablemouse", false );

if ( !dbg_disablemouse )
{
AcquireMouse();
}

.
.
.

Core::ConsoleFloat phys_friction ( "phys_friction", 0.04 );




What this does, is declare a boolean configuration variable. If a variable in the config file has been read in, which has the same name, then the config variable will get the value that was read from the config file. Otherwise, it will use the default value of false, passed to the ConsoleBool constructor.

If a variable was read from the config file, but it's of a different type, float for example, then the ConsoleBool will overwrite the value of the config variable with the default value of false.

Share this post


Link to post
Share on other sites
Quote:
Original post by snk_kid
Quote:
Original post by bjogio
This is how I achieve this in my engine "iltuomondofuturo":....


That code has some "issues", like SetValue passing arguments by value and GetValue returning by value. It also uses manual type erasor as opposed to automatic type erasor which is much nicer (like Boost.Any does).


I do not like Boost.any. I do pass argument for value cause:

typedef ConfigVar<int> ConfigInt;
typedef ConfigVar<unsigned int> ConfigUnsignedInt;
typedef ConfigVar<float> ConfigFloat;
typedef ConfigVar<bool> ConfigBool;
typedef ConfigVar<std::string> ConfigString;

for the first 4 I do not have problems and for the last one I have a memcpy. Config var do not change a lot during program execution. When you run the first time, possibly during execution if the user change some settings and when you reset it all so is ok for me.
You can make SetValue virtual and change it in:

void SetValue(T& Val) { Value = Val; }

for std::string specialization (I can make it, uh yes I can, a stupid mistake), or change for everything.
As I said is a little tricky implementation but better than nothing :)
Do you see any other errors? I really like to improve my code when I can.

Share this post


Link to post
Share on other sites
Why don't you just load everything as a string, then use another class to interpret the string as you load it?

That way you can just do something like:

typedef map<string,string> string_map;

I've developed an Echo class that echos the info in from or out to file according to the type. As is the case in Lisp, I interpret anything with a '+-0123456789.' as a float, and anything with just a '+-0123456789' as an int, and everything else is a string. There are excellent C++ Standard Library algorithms that can be used at the heart of this.

Now I can use very simple containers and basically one command 'readline(T &)' to read the data; it is interpreted automatically. Simple.

--random

Share this post


Link to post
Share on other sites
Quote:
Original post by bjogio
for the first 4 I do not have problems


What you can do is use some template metaprogramming & type traits to either pass by value for built-in primitive types or by (constant) reference for all other types, i have code for that you can use:


#include <boost/mpl/eval_if.hpp>
#include <boost/mpl/identity.hpp>
#include <boost/type_traits/add_reference.hpp>
#include <boost/type_traits/is_fundamental.hpp>

template < typename Tq >
struct param_type : boost::mpl::eval_if<
boost::is_fundamental<Tq>,
boost::mpl::identity<Tq>,
boost::add_reference<Tq const>
> {};


usage:


template < typename Tp >
struct foo {

void bar(typename param_type<Tp>::type);

};


Quote:
Original post by bjogio
for the last one I have a memcpy.


I didn't see memcpy being used in the above code, if you mean you are using memcpy to copy to and from std::string this wont work correctly use it's copy constructor for that, You should only use memcpy for POD-types (POD == Plain old data type a technical term used in the C++ standard).

Quote:
Original post by bjogio
Config var do not change a lot during program execution...


I think a slight a confusion here, when i mentioned automatic type erasor i mean a C++ idiom, its kinda hard to explain without a code example.

Quote:
Original post by bjogio
Do you see any other errors? I really like to improve my code when I can.


I'll look into and post some improvements if you want me to.

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!