9

My aim is to set up a data structure to store the settings of my application.

In PHP I would just write...

$settings = array(
    "Fullscreen" => true,
    "Width"      => 1680,
    "Height"     => 1050,
    "Title"      => "My Application",
);

Now I tried to create a similar structure in C++ but it can't handle different datatypes yet. By the way if there is a better way of storing such settings data, please let me know.

struct Setting{ string Key, Value; };

Setting Settings[] = {
    ("Fullscreen", "true"),     // it's acceptable to store the boolean as string
    ("Width", "1680"),          // it's not for integers as I want to use them later
    ("Height", 1050),           // would be nice but of course an error
    ("Title", "My Application") // strings aren't the problem with this implementation
};

How can I model a structure of an associative array with flexible datatypes?

danijar
  • 32,406
  • 45
  • 166
  • 297

6 Answers6

8

An associative data structure with varying data types is exactly what a struct is...

struct SettingsType
{
    bool Fullscreen;
    int Width;
    int Height;
    std::string Title;
} Settings = { true, 1680, 1050, "My Application" };

Now, maybe you want some sort of reflection because the field names will appear in a configuration file? Something like:

SettingsSerializer x[] = { { "Fullscreen", &SettingsType::Fullscreen },
                           { "Width",      &SettingsType::Width },
                           { "Height",     &SettingsType::Height },
                           { "Title",      &Settings::Title } };

will get you there, as long as you give SettingsSerializer an overloaded constructor with different behavior depending on the pointer-to-member type.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 2
    That's not so elegant since you will need to change this file each time you need to add a new settings type. – AlexTheo Aug 24 '12 at 21:05
3

C++ is a strongly typed language. The containers hold exactly one type of object so by default what you are trying to do cannot be done with only standard C++.

On the other hand, you can use libraries like boost::variant or boost::any that provide types that can hold one of multiple (or any) type, and then use a container of that type in your application.

Rather than an array, you can use std::map to map from the name of the setting to the value:

std::map<std::string, boost::variant<bool,int,std::string> >
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • The `boost:variant` type is what I need, but maybe there is a solution without using a library for this task. Because I will never need flexible type in this game application again. – danijar Aug 24 '12 at 21:05
  • [Is C a strongly typed language?](http://stackoverflow.com/questions/430182/is-c-strongly-typed) I think this discussion applies equally well to C++. In the context here, perhaps it would be more accurate to say that C++ is a statically typed language. – Code-Apprentice Aug 24 '12 at 21:12
  • @sharethis: I would really go for a prepacked solution. If you don't mind the extra cost in memory, you can create a `struct` that contains a `selector` determining the type of data and then one field for each type. But note that even this will require more memory and you will have to spend some time getting it to work (finding the proper interface so that it is not too much burden on user code...) – David Rodríguez - dribeas Aug 24 '12 at 21:13
  • @Code-Guru: I have heard that argument before, and I don't really want to revisit it... I, personally, consider C++ as a strongly typed language if you wish. But then again, I code with no casts and no `void*`... – David Rodríguez - dribeas Aug 24 '12 at 21:15
  • Fair enough. Other than touching on that small holy war, your answer has some good information ;-) – Code-Apprentice Aug 24 '12 at 21:25
  • As a computer game it is a performance related application where I don't need to "waste" my memory. That's the only problem. The other is that a struct holding all types have to be typed every line of the declarating. – danijar Aug 24 '12 at 21:30
  • @sharethis: Before you go into the holy war of performance, measure. How many settings will your *game* have? 100? 1000? If you intend on creating a product you need to know where to spend your time and where it does not matter. Any desktop computer nowadays has a couple of Gb of memory. Even mobile phones have memory in the order of hundreds of megs... The best generic solution you can use is `boost::variant`. – David Rodríguez - dribeas Aug 25 '12 at 00:26
2
#include <map>
#include <string>

std::map<std::string,std::string> settings;

settings.insert("Fullscreen","true");
settings.insert("Width","1680");
settings.insert("Height","1050");
settings.insert("Title","My Application");

Could be one way of doing it if you want to stick with the STL.

Rapptz
  • 20,807
  • 5
  • 72
  • 86
  • I already tried maps. Unfortunately you answer can't solve the problem of integer and boolean data I need for the values. – danijar Aug 24 '12 at 21:07
  • @sharethis You would need to add an extra layer to convert the `std::string` value to a `bool` or an `int`. Not entirely impossible, but it certainly will take a bit of work. – Code-Apprentice Aug 24 '12 at 21:23
  • That's the reason I don't want to store the `Width` as a string. Please look at my example code. :-) – danijar Aug 24 '12 at 21:25
2

One solution could be to define the ISetting interface like:

class ISetting{
public:
  virtual void save( IStream* stream ) = 0;
  virtual ~ISetting(){}
};

after that you can use a map in order to store your settings:

std::map< std::string, ISetting* > settings;

One example of the boolean setting is:

class BooleanSetting : public ISetting{
private:
  bool m_value;
public:
  BooleanSetting(bool value){
    m_value = value
  }

  void save( IStream* stream ) {
    (*stream) << m_value;
  }

  virtual ~BooleanSetting(){}
};

in the end:

settings["booleansetting"]=new BooleanSetting(true);
settings["someothersetting"]=new SomeOtherSetting("something");
AlexTheo
  • 4,004
  • 1
  • 21
  • 35
  • I really like this approach. My only quesion now is how to declare the array (instead of map) in a simple way, like `= {...}`. – danijar Aug 24 '12 at 21:18
  • @sharethis Why do you need the array? I think that std::map is fit better here. – AlexTheo Aug 24 '12 at 21:24
  • Because I want to declare the hole config in a single command, like in my example code. – danijar Aug 24 '12 at 21:31
  • @sharethis I've edited my post, take a look you can make a setting per line that is quite similar with what you go to achieve with the array. – AlexTheo Aug 24 '12 at 21:33
  • It is similar, but I have to write settings in each line. By the way there is an error at the `<<` saying "no operator matches". – danijar Aug 24 '12 at 21:39
  • Because the IStream is undefined, you need to define it or use another stream, std::stream. Actually it is up to you how to define and implement the save method it also could be the >> operator instead of method. – AlexTheo Aug 24 '12 at 21:41
  • Understand :-). An how can I finally create a struct holding one of the settings classes? (To build an array of the struct.) – danijar Aug 24 '12 at 21:58
  • Now I use a single struct and it works well. I can live with that. – danijar Aug 25 '12 at 08:17
1

One possible solution is to create a Settings class which can look something like

class Settings {
    public:
        Settings(std::string filename);

        bool getFullscreen() { return Fullscreen; }
        // ...etc.

    private:
        bool Fullscreen;
        int Width;
        int Height;
        std::string Title;
};

This assumes that the settings are stored in some file. The constructor can be implemented to read the settings using whatever format you choose. Of course, this has the disadvantage that you have to modify the class to add any other settings.

Code-Apprentice
  • 81,660
  • 23
  • 145
  • 268
0

To answer your question, you could use boost::any or boost::variant to achieve what you would like. I think variant is better to start with.

boost::variant<
    std::string,
    int,
    bool
  > SettingVariant;

std::map<std::string, SettingVariant> settings;

To not answer your question, using typeless containers isn't what I would recommend. Strong typing gives you a way to structure code in a way that the compiler gives you errors when you do something subtly wrong.

struct ResolutionSettings {
  bool full_screen;
  size_t width;
  size_t height;
  std::string title;
};

Then just a simple free function to get the default settings.

ResolutionSettings GetDefaultResolutionSettings() {
  ResolutionSettings settings;
  settings.full_screen = true;
  settings.width = 800;
  settings.height = 600;
  settings.title = "My Application';
  return settings;
}

If you're reading settings off disk, then that is a little different problem. I would still write strongly typed settings structs, and have your weakly typed file reader use boost::lexical cast to validate that the string conversion worked.

ResolutionSettings settings;
std::string str = "800";
size_t settings.width = boost::lexical_cast<size_t>(str);

You can wrap all the disk reading logic in another function that isn't coupled with any of the other functionality.

ResolutionSettings GetResolutionSettingsFromDisk();

I think this is the most direct and easiest to maintain (especially if you're not super comfortable in C++).

Tom Kerr
  • 10,444
  • 2
  • 30
  • 46
  • 1
    Using `std::map` may be helpful too. – GWW Aug 24 '12 at 20:59
  • @Tom Kerr. Could you explain it to me? I never heard of `boost`. It also would be great to not use a library for this *simple* task. – danijar Aug 24 '12 at 21:00
  • It's not really "simple". PHP is dynamically typed so something like this is natural, but storing arbitrary data isn't something you normally do in a statically typed language. Figure out beforehand what you wanna store. – Cubic Aug 24 '12 at 21:01
  • @GWW. I tried using maps before. The problem about maps was declaring their fields in a single step. I don't need houndreds of `map.insert`s. – danijar Aug 24 '12 at 21:02
  • @Cubic. I wrote simple in *italic* letters because I know it isn't a basic task in C++. I want to store integers, strings and boolean for now. :-) – danijar Aug 24 '12 at 21:03
  • @sharethis It actually would be great to get used to using boost. It is an awesome, well tested library. It's also a proving ground for new C++ ideas, ideas that end up in the standards of the language. As an example `std::shared_ptr` started as `boost::shared_ptr`. So familiarity with boost can help you in the long run. – Tom Kerr Aug 24 '12 at 21:21
  • @sharethis Somewhat of a tangent, though you may find it helpful. http://stackoverflow.com/questions/8400128/xml-vs-yaml-vs-json-for-a-2d-rpg/8402811#8402811 – Tom Kerr Aug 24 '12 at 21:26
  • I trust you in this fact. But this time I am developing a computer game. So I can't employ unnecessary overhead. – danijar Aug 24 '12 at 21:27