Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Configuration structs vs setters

Tags:

c++

I recently came across classes that use a configuration object instead of the usual setter methods for configuration. A small example:

class A {  
   int a, b;  
public:  
   A(const AConfiguration& conf) { a = conf.a; b = conf.b; }  
};  

struct AConfiguration { int a, b; };

The upsides:

  • You can extend your object and easily guarantee reasonable default values for new values without your users ever needing to know about it.
  • You can check a configuration for consistency (e.g. your class only allows some combinations of values)
  • You save a lot of code by ommiting the setters.
  • You get a default constructor for specifying a default constructor for your Configuration struct and use A(const AConfiguration& conf = AConfiguration()).

The downside(s):

  • You need to know the configuration at construction time and can't change it later on.

Are there more downsides to this that I'm missing? If there aren't: Why isn't this used more frequently?

like image 953
pmr Avatar asked Nov 12 '09 16:11

pmr


2 Answers

Whether you pass the data individually or per struct is a question of style and needs to be decided on a case-by-case basis.

The important question is this: Is the object is ready and usable after construction and does the compiler enforce that you pass all necessary data to the constructor or do you have to remember to call a bunch of setters after construction who's number might increase at any time without the compiler giving you any hint that you need to adapt your code. So whether this is

 A(const AConfiguration& conf) : a(conf.a), b(conf.b) {}

or

 A(int a_, int b_) : a(a_), b(b_) {}

doesn't matter all that much. (There's a number of parameters where everyone would prefer the former, but which number this is - and whether such a class is well designed - is debatable.) However, whether I can use the object like this

A a1(Configuration(42,42));
A a2 = Configuration(4711,4711);
A a3(7,7);

or have to do this

A urgh;
urgh.setA(13);
urgh.setB(13);

before I can use the object, does make a huge difference. Especially so, when someone comes along and adds another data field to A.

like image 156
sbi Avatar answered Sep 22 '22 14:09

sbi


Using this method makes binary compatibility easier.

When the library version changes and if the configuration struct contains it, then constructor can distinguish whether "old" or "new" configuration is passed and avoid "access violation"/"segfault" when accessing non-existant fields.

Moreover, the mangled name of constructor is retained, which would have changed if it changed its signature. This also lets us retain binary compatibility.

Example:

//version 1
struct AConfiguration { int version; int a; AConfiguration(): version(1) {} };
//version 2
struct AConfiguration { int version; int a, b; AConfiguration(): version(2) {} };

class A {  
   A(const AConfiguration& conf) {
     switch (conf.version){
       case 1: a = conf.a; b = 0;  // No access violation for old callers!
       break;
       case 2: a = conf.a; b = conf.b;  // New callers do have b member
       break;
     }
   }  
};  
like image 21
P Shved Avatar answered Sep 23 '22 14:09

P Shved