0

I have a configuration file which gets read in, parsed and put into structures at the beginning of my programs run time.

The problem I am having is that I want these structures to be constant since the values in them should not change during the programs lifespan.

Currently I am doing the following:

config.h

#pragma warning(push)
#pragma warning(disable: 4510) /*-- we don't want a default constructor --*/
#pragma warning(disable: 4610) /*-- we don't want this to ever be user instantiated --*/

typedef struct SerialNode {
private:
    void operator=(SerialNode&);
public:
    const char* const port;
    const char* const format;
} SerialNode;

#pragma warning(pop)

typedef std::map<const char*, const SerialNode*, MapStrComp> SerialMap;
SerialMap SerialConfig;

config.cpp

/*-- so we don't fall out of scope --*/
SerialNode* global_sn;
SerialNode local_sn = {port, format};
global_sn = new SerialNode(local_sn);
SerialConfig[key_store] = global_sn;

This works fine. However my problem is that now I am dealing with more complicated configuration data which requires me to pull a structure back out of the list, modify it and then put it back.

Obviously I can't modify it, so the solution would be something like:

SerialNode* global_sn;
SerialNode* old_sn = SerialConfig[key_store];
SerialNode local_sn = {port, format, old_sn->old_data, old_sn->more_old_data};
global_sn = new SerialNode(local_sn);
SerialConfig[key_store] = global_sn;
delete old_sn;

But this strikes me as bad programming practice. Is there is a better way to achieve what I'm going for which doesn't require such a hacked looking solution?

For reference, I'm using Visual Studio 2010

Serdalis
  • 10,296
  • 2
  • 38
  • 58
  • remove the `const`? seems like ur making something `const` and then working around it. however, your proposed solution is actually a kinda functional style programming (except for modifying `SerialConfig`) so it's not too bad. y r u using `new` btw? – Kal Sep 26 '13 at 21:01
  • 1
    Those pragmas, I don't think they mean what you think they mean. They merely suppress warnings that you should probably take care of in some other way. – Thomas Sep 26 '13 at 21:02
  • @Kal That's the point. It should be `const` and I shouldn't be working around it the way I am. But I am deeming it acceptable for the configuration code to work around the `const` because it needs to be set once. No other code should be able to modify this though. – Serdalis Sep 26 '13 at 21:03
  • other code can modify it, just the way u did :) one solution is to have a `const` pointer to a non-`const` instance which is hidden in an implementation file for the config code or sth tho. that way other code sees only a `const SerialNode` but your code has access to underlying non-`const` `SerialNode`. – Kal Sep 26 '13 at 21:04
  • @Thomas They suppress warnings I don't think are useful to my code since I don't want a default constructor or a user constructor. I know they are merely suppressing the warning in the compiler but I like to run with 0 warnings on Warning Level 4. – Serdalis Sep 26 '13 at 21:06
  • @Kal Ah sorry I should have probably added that other code doesn't have access to the `SerialConfig` map, only a getter. Maybe that is indeed enough to ensure no funny business... – Serdalis Sep 26 '13 at 21:09
  • @Serdalis If you want to compile without warnings, it's better to just fix them. In this case, just add a custom two-arg constructor that fills out those const fields. – Thomas Sep 26 '13 at 21:19
  • @Thomas which breaks my initialisation code because the structure becomes `non-aggregate`. Which does point to a problem in the initialisation code i'll admit. – Serdalis Sep 26 '13 at 21:23
  • 1
    I meant to define the ctor and then use it as well ;) – Thomas Sep 26 '13 at 21:29
  • I didn't read in too deep, but if you can use C++11 maybe this can direct you to a completely compile-time configurable structure: [Creating an array initializer from a tuple or variadic template parameters](http://stackoverflow.com/questions/18251815/creating-an-array-initializer-from-a-tuple-or-variadic-template-parameters) – πάντα ῥεῖ Sep 26 '13 at 21:38

4 Answers4

2

As always, the best thing you can do is not re-implement something that has already been written. There are a large number of libraries and frameworks that will help with serialization for c++:

Ideally the serialization framework you choose will exactly recreate the data graph that you are trying to store. Regardless of whether you have done any fixup, your goal will likely be to only provide const access to the global configuration data. Just make sure that mutators (including non const pointers) are not exposed via a header file.

Dan O
  • 4,323
  • 5
  • 29
  • 44
  • I think you put this on the wrong question there's no serialisation going on here :D. – Serdalis Sep 26 '13 at 21:16
  • @Serdalis "I have a configuration file which gets read in, parsed and put into structures at the beginning of my programs run time." – Dan O Sep 26 '13 at 21:35
  • That's not serialisation. I'm also not having trouble reading / parsing the file. Just storing into objects. – Serdalis Sep 26 '13 at 22:18
  • You're having trouble hiding the fact that something (in this case, your serialization code) needs to mutate some objects. A good serialization library will non intrusively create and construct your data graph, leaving you to set up accessors as you would normally. – Dan O Sep 26 '13 at 22:32
  • Sorry about the confusion, I see where you are coming from now. My definition of serialisation was too strict it seems (too many com ports). I don't think I can currently use any of those. But protocol Buffers does look quite promising. Thanks for the answer, I'll definitely keep it in mind. – Serdalis Sep 26 '13 at 22:43
1

This is not an answer to your question, just some observations to your code.

  • You don't need the typedef struct SerialNode { ... } SerialNode;, this is a idiom. In , you just write struct SerialNode { ... }; and use SerialNode as a type name.

  • If you want to prevent a default constructor, make it private as you already do with the assignment operator

    class SerialNode {
    private:
        SerialNode();
        SerialNode &operator=(SerialNode&);
    ...
    };
    
  • Don't use char* members, use std::string instead. C++ strings are much easier and safer to use than plain char pointers and the associated heap allocation.

  • Same goes for the map key; if you use std::string as a key, you don't need MapStrComp anymore, because std::string already provides an appropriate comparison.

Olaf Dietsche
  • 72,253
  • 8
  • 102
  • 198
  • Awesome I didn't realise you don't need the typedef in C++. When I add the default constructor my struct becomes `non-aggregate` which breaks my initialisation code. Unfortunately using `std::string` would require too many changes to supporting code, but you are correct. – Serdalis Sep 26 '13 at 21:15
1

Probably nicer is to wrap the whole thing in a singleton class:

class Config {
  public:
    static Config const& get() { return *config; }
    static void load();

    SerialNode const* operator[](const char*);

  private:     
    static Config* config;

    SerialMap map;
};

void Config::load() {
  config = new Config();
  // put things into it
}

Disclaimer: not tested, and haven't used C++ in a while, so there might be some syntax errors :)

Thomas
  • 174,939
  • 50
  • 355
  • 478
  • I think the gist of your answer is correct. Remove the const from the struct and put it on a higher level. I'm still getting used to using const so I'm over consting at the moment. I pass the config object around though, no need for a singleton, this is part of a much bigger class. – Serdalis Sep 26 '13 at 21:30
  • Casting away const-ness with `const_cast` and writing to the object is undefined behavior. Don't do this. – David Rodríguez - dribeas Sep 26 '13 at 22:02
  • Thanks. I've removed that option. I didn't know because `const_cast` scares me so I avoid it altogether :) – Thomas Sep 27 '13 at 08:09
1

The simple answer is what Thomas suggest, but correctly done (that is, not causing undefined behavior):

Create a mutable configuration object but pass it to the rest of the components by constant reference. When you create (and where you maintain) the real object you can change it, but the rest of the application won't be able to modify the config. A common pattern I have used in the past was:

class SomeObject {
    Configuration const & config;
public:
    SomeObject(Configuration const & config) : config(config) {}
    void f() {
        if (config.someParam()) { ...
// ...

void loadConfiguration(Config & config) { ... }
int main() {
   Configuration config;
   loadConfiguration(config);  // config is a non-const &, can modify
   SomeObject object(config);  // object holds a const&, can only read
   object.f();
// ...
David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489