Yet another C++ Object initialization interrogation

梦想的初衷 提交于 2020-01-15 10:18:53

问题


I have this class that has many class members, and a lot of different constructors.

Until now, I used a constructor initialization list in each of the constructors that I have, tuning each member the way I wanted.

This is quite tedious, because everytime I add a new member to my class, I have to visit each constructor and update the initialization list to add a default value to this member.

So, I thought I would add a method to initialize the values I need. Problem! Since the method is executed after the initialization list, the particular values I put in this init list are overriden by my method.

Quick exemple:

class A
{
public:
  A();
  A( B b );
  A( int i );
  // A( .... ); plenty of them


private:
  int member1, m2, m3,m4;
  bool b1,b2, b3;
  // ....
  // every time I add a member I have to modify the initialization lists

  // solution: agregate member initialization in a member function: 
  void init_members();    
}

// init list constructors
A::A() : m1(false), m2(false), m3(false), m4(true) .... // looong list
{
}

A::A( B b) : m1(b.state()), m2(false), m3(false), ... // loong list
{
}

// problem, if I use init_members:
void A::init_members()
{
  m1 = false;
  m2 = false;
  m3 = false;
// ...
}

A::A( int i ) : m1( true)
{
  init_members(); // overrides m1 !!!
}

So, my question: can I mix list initializer and method initializers, so that list initializers have precedence over the method initializer?
In my example above, I want m1 to stay true for the last constructor.

Note: I know I could move the initialization list after the method call, but this means I'd assign twice the values to the members : once in init_members(), then overriding it in the constructor. Not optimal enough :-)

I was hoping for some little trick, if you have that in stock.


回答1:


In C++11 one option could be constructor delegation, where one constructor simply calls one of the other constructors, so as to avoid duplicating code.

This is what it looks like:

class A {
public:
    A(int,int,double);
    A(int,int);
    A(double);
    A();
private:
    ...
};

A::A(int a,int b,double c) {
    // the real work to initialize the class
}

// A(int,int) delegates to A(int,int,double), passing along a
// default value for the double
A::A(int a,int b) : A(a,b,0.0) {}

A::A(double c) : A(1,2,c) {} // A(double) delegates to A(int,int,double)

A::A() : A(1.0) {} // A() delegates to A(double)

Make sure you don't create any loops. Also typically you'll want a single constructor to do most of the real work while the others just marshal the values they want to pass to that constructor. We'll call this the 'designated constructor'. The designated constructor should be the one that takes the most parameters and doesn't use any default values. Eventually all the constructors should call the designated constructor, directly or indirectly.

Note the pattern: constructors that use some default values pass those defaults along to constructors that use fewer defaults, until you arrive at the function with no defaults at all. This is the opposite of what you're trying to do with your init_members() method. You have a function that sets all the defaults, and then you try to override some of them. If you can't use C++11 features, you would be better off emulating the designated constructor pattern: init_members() will be your designated initializer and it will not have any defaults. You can use an initializer method for each constructor which takes the arguments it's given, and throws in a few default values to call another init_members overload.

However one issue with the designated initializer/constructor is that the defaults are scattered all over the place. Another option in C++11 besides delegation is 'in-class initialization', which allows all the default values to be gathered together.

class A {
public:
    A(int,int,double);
    A(int,int);
    A(double);
    A();
private:
    int a = 1,b = 2; // in-class initialization gathers all the defaults together
    double c = 1.0;
};

Given the above, all constructors will automatically initialize the member values to those defaults unless you explicitly initialize it to something else in that constructor.

A::A(int a,int b,double c) : a(a), b(b), c(c) {}
A::A(int a,int b) : a(a), b(b) {} // member c is automatically initialized to 1.0
A::A(double c) : c(c) {} // members a and be are automatically initialized to 1 and 2
A::A() {}; // all members are initialized with their in-class values.

Here's an example of using init_members():

class A {
public:
    A(int a,int b,double c) { init_members(a,b,c); }
    A(int a,int b) { init_members(a,b); }
    A(double c) {init_members(c);}
    A() { init_members(); }
private:
    void init_members(int,int,double) { ... }
    void init_members(int a,int b) { init_members(a,b,1.0); }
    void init_members(double c) { init_members(1,2,c); }
    void init_members() { init_members(1.0); }
    ...
};

This method value initializes members before init_members() can be called, so members are initialized twice. I'm not sure there's a way to fix that in C++03.




回答2:


For cases like this, I do not use the Base/Member Initializer List (the members have "garbage" or "default-constructor" values at that point in the constructor), and I use the init_() function (called from the constructor-body). Then, the constructors call the init_() function, and there is a single maintenance point.

Similarly, my clear() function would also call the init_() function, for a single maintenance point of "default" values.

For your case, it would be:

A::A(void)
//...no Base/Member-Initializer list...
{
  init_members();
}

A::clear(void)
{
  init_members();
}

...and the override:

A::A(int override_m1)
{
  init_members();
  m1 = override_m1;
}



回答3:


Hm, I don't want to know what names your colleagues give you if they spot a class like this... Imagine what happens if they have to repair or extend a monster of this kind. I newly assigned a task to switch some of the many As we have in our code base (and which became unmaintainable) to something like this:

class AKeyValueStorage {
    // would be some kind of shared storage if meant to be 
    // copyable and don't forget moving if your're on c++11!
    std::map<std::string, boost::any> mMembers;
public:
    template<class Key, class T>
    T const & Get(Key const & pKey) const
    {
        auto tTmp = mMembers.find(ToString(pKey));
        if (tTmp != mMembers.end()) {
            return boost::any_cast<T const &>(*tTmp); 
        }
        // throw if none, or return default
    }

    template<class Key, class T>
    void Set(Key const & pKey, T const & pValue) const
    {
        mMembers[ToString(pKey)] = pValue; // replace if found, insert if none
    }
};


来源:https://stackoverflow.com/questions/9911309/yet-another-c-object-initialization-interrogation

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!