nass nass - 2 months ago 11
C++ Question

Non-member initialization in constructor initializer list

Confused? Me too...
Consider the following

typedef std::map<std::string , double> Thresholds;

class Foo
{
public:
Foo( const double & _toxicThres , const double & _zeroThres )
: thresholds
(
MapInitializer<std::string , double>()
.Add("toxic" , _toxicThres)
.Add("zero" , _zeroThres)
)

private:
Thresholds thresholds;
};


The above works fine and initializes an
std::map
in the constructor's member initialisation list. Now consider this:

typedef std::map<std::string , double> Thresholds;
struct CommonData
{
Thresholds thresholds;
};

class Foo //a mixin
{
public:
Foo( Thresholds & thresholds , const double & _toxicThres , const double & _zeroThres )
: thresholds
(
MapInitializer<std::string , double>()
.Add("toxic" , _toxicThres)
.Add("zero" , _zeroThres)
)
};

class Bar //another mixin
{
public:
Bar( Thresholds & thresholds , const double & _warningThres , const double & _zeroThres)
: thresholds
(
MapInitializer<std::string , double>()
.Add("warning" , _warningThres)
.Add("zero" , _zeroThres)
)
};

class OtherGasThreshold{/*...*/}; //yet another mixin, etc...

template<typename ThresholdMixin> //Foo , Bar , or others ...
class ThresholdSensor : public ThresholdMixin
{
public:
ThresholdSensor(double val1 , double val2)
: ThresholdMixin(cd.thresholds, val1 , val2)
{}

private:
CommonData cd;
};


Note that the
MapIniializer
code comes from here, and is

template<class K, class V>
class MapInitializer
{
std::map<K,V> m;
public:
operator std::map<K,V>() const
{
return m;
}

MapInitializer& Add( const K& k, const V& v )
{
m[ k ] = v;
return *this;
}
};


Of course the above would not compile, but is there any way to init the map in
ThresholdSensor::CommonData
in one of the mixins during constructor init. ie can I pass by reference the map, init it in the mixins constructor?

Answer

In the constructor in question, thresholds gets passed as a parameter to the constructor.

The initializer syntax is for initializing superclasses, and class members. For everything else, that's what the constructor's body is for:

class Bar
{
    public: 
        Bar( Thresholds & thresholds,
             const double & _warningThres,
             const double & _zeroThres)
    {
       thresholds=MapInitializer<std::string , double>()
            .Add("warning" , _warningThres)
            .Add("zero" , _zeroThres)
    }
};

There are no superclasses or other class members in this example, so this works fine. I do not see any explicit dependency in your example that requires thresholds to be initialized in the initialization section of the constructor.

But suppose there is. Let's say there was a dependency of some sort between thresholds, and either the superclass, or a class member, and due to some unspecified dependency it becomes necessary to initialize thresholds first, before initializing the other object. The class member, or the superclass must be initialized in the initialization section of the constructor, so we need to initialize thresholds there, too.

It's much easier if we use a concrete example:

class Bar
{
    public: 
        Bar( Thresholds & thresholds,
             const double & _warningThres,
             const double & _zeroThres);

    class private_bar {
    public:
         private_bar(Thresholds &thresholds);
    };

private:
    private_bar secret;
};

Let's say that Bar's constructor needs to construct secret but only after initializing thresholds, that gets passed to private_bar's constructor. It's not hard to imagine a situation where this happens. private_bar's constructor uses an initialized thresholds, and that's that. Now, you have initialize the secret member in the initialization section of Bar's constructor, but thresholds needs to be initialized before this happens. I believe that this is what your question boils down to.

In this situation the solution typically takes the following generic design pattern:

class Bar
{

       static Thresholds &init_thresholds(Thresholds &thresholds)
       {
          thresholds=MapInitializer<std::string , double>()
            .Add("warning" , _warningThres)
            .Add("zero" , _zeroThres)

          return thresholds;
       }

    public: 
        Bar( Thresholds & thresholds,
             const double & _warningThres,
             const double & _zeroThres)
             : secret(init_thresholds(thresholds))
        {
        }

    class private_bar {
    public:
         private_bar(Thresholds &thresholds);
    };

private:
    private_bar secret;
};

And that's how it's "possible to do non-member initialization in the constructor list of a class", to answer your question. If it is truly necessary to do that, it must be because something else needs to be initialized in the constructor list, first. Otherwise you can simply initialize it in the main constructor body.

But if something else needs to be initialized in the constructor list, then you just "piggy-back" on top of that construction, in order to initialize your non-member object, using a helper function.