构造函数初始值设定项列表中的非成员初始化

Non-member initialization in constructor initializer list

本文关键字:成员 初始化 列表 构造函数      更新时间:2023-10-16

困惑?我也是。。。考虑以下

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; 
};

上面的操作很好,并初始化构造函数的成员初始化列表中的std::map。现在考虑一下:

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;
};

注意,MapIniializer代码来自这里,并且是

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;
    }
};

当然,上面的内容不会编译,但在构造函数初始化期间,有没有任何方法可以在其中一个mixin中初始化ThresholdSensor::CommonData中的映射。即我可以通过引用传递映射,在mixins构造函数中初始化它吗?

基本子对象是在数据成员之前构造的,因此基本初始值设定项通常不能使用派生的数据成员。

我只想保持简单,并有一个正常的成员功能:

struct Foo
{
    void Init(Thresholds & thresholds)
    {
         thresholds.emplace("foo", 1.0);
         thresholds.emplace("bar", 1.5);
    }
    // ...
};
template <typename Mx>
struct Thing : Mx
{
    Thresholds thresholds;
    Thing() { Mx::Init(thresholds); }
    //        ^^^^^^^^^^^^^^^^^^^^^
    // ...
};

用法:

Thing<Foo> x;

在有问题的构造函数中,thresholds作为参数传递给构造函数。

初始化器语法用于初始化超类和类成员。对于其他一切,这就是构造函数的主体的作用:

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

在这个例子中没有超类或其他类成员,所以这很好。在您的示例中,我没有看到任何需要在构造函数的初始化部分初始化thresholds的显式依赖项。

但假设存在。假设thresholds与超类或类成员之间存在某种依赖关系,由于某些未指定的依赖关系,在初始化其他对象之前,必须先初始化thresholds。类成员或超类必须在构造函数的初始化部分初始化,所以我们也需要在那里初始化thresholds

如果我们用一个具体的例子:,那就容易多了

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

假设Bar的构造函数需要构造secret,但只有在初始化thresholds之后,它才会传递给private_bar的构造函数。不难想象会发生这种情况。private_bar的构造函数使用初始化的thresholds,仅此而已。现在,您已经在Bar的构造函数的初始化部分初始化了secret成员,但在此之前需要初始化thresholds。我相信这就是你的问题。

在这种情况下,解决方案通常采用以下通用设计模式:

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;
};

这就是为什么"可以在类的构造函数列表中进行非成员初始化",以回答您的问题。如果真的有必要这样做,那一定是因为else需要首先在构造函数列表中初始化。否则,您可以简单地在构造函数主体中初始化它。

但是,如果需要在构造函数列表中初始化其他东西,那么您只需在该构造的顶部"背负",以便使用辅助函数初始化非成员对象。