【问题标题】:Complex initialization of const fieldsconst 字段的复杂初始化
【发布时间】:2011-03-16 10:23:07
【问题描述】:

考虑这样一个类:

class MyReferenceClass
{
public:
    MyReferenceClass();
    const double ImportantConstant1;
    const double ImportantConstant2;
    const double ImportantConstant3;
private:
    void ComputeImportantConstants(double *out_const1, double *out_const2, double *out_const3);
}

有一个例程 (ComputeImportantConstants) 在运行时计算三个常量。假设计算相当复杂,并且本质上一次产生所有三个值。此外,结果取决于构建配置,因此不能对结果进行硬编码。

有没有一种明智的方法可以将这些计算值存储在类的相应 const double 字段中?

如果没有,您能否建议一种更自然的方式在 C++ 中声明这样的类?

在 C# 中,我会在这里使用带有静态构造函数的静态类,但这不是 C++ 中的选项。我也考虑过将 ImportantConstant1..3 设为非常量字段或函数调用,但两者似乎都逊色。

我发现初始化 const 字段的唯一方法是到 use initializer lists,但似乎无法在这样的列表中传递多输出计算的结果。

【问题讨论】:

  • 如果可以的话,能说说ComputeImportantConstants是怎么实现的吗?是不是比较长?这三个常数如何相互作用,还涉及哪些其他因素?

标签: c++ initialization constants ctor-initializer


【解决方案1】:

你为什么不能这样做:

MyReferenceClass ComputeImportantConstants(){
    //stuff to compute
    return MyReferenceClass( const1, const2, const3 );
}

MyReferenceClass{
public:
    MyReferenceClass(double _1, double _2, double _3) 
        : m_Const1(_1),
        m_Const2(_2),
        m_Const3(_3){}

    double getImportantConst1() const { return m_Const1; }
    double getImportantConst2() const { return m_Const2; }
    double getImportantConst3() const { return m_Const3; }
private:
    const double m_Const1,
                 m_Const2,
                 m_Const3;
};

这样并让计算函数变成工厂函数?

【讨论】:

  • 一个改进的想法:在 ComputeImportantConstants() 中创建一个静态变量,并在计算完所有内容后返回此变量。这样,ComputeImportantConstants 的后续调用不会触发额外的计算。
  • C++ nitpick:从函数返回 const double 没有多大意义。它只会使调用者的生活变得比必要的更难,而不会提高安全性。毕竟成员变量是按值返回的。
  • 没有方法而只保存数据(POD 类型)的结构没有任何问题。另一方面,如果正在访问的内容隐藏在后面,通常更容易影响程序中的更改一个函数调用。由于后者,大多数人会更喜欢封装。
  • 挑剔的是错误的 - 没有返回 const double,只有 doubleconst 方法返回,即保证不更改内容的方法。
  • @Chris:如果您查看更改历史记录,您会发现大约两年前 是一个 const double。 ;-)
【解决方案2】:

首先 - 你可以做坏事:在 ComputeImportantConstants() 中抛弃 const 并将值放在那里。但是不要这样做,因为那样你会对编译器撒谎,它会试图找到最糟糕的回报方式。

秒:

做这样的事情:

class A
private:
  double important1;
  double important2;
  double important3;
  A() { ComputeImportantConstants(); } //no need for parameters, it accesses the members
  void ComputeImportantConstants();
public:
  inline double GetImportant1() { return important1; }
  inline double GetImportant2() { return important2; }
  inline double GetImportant3() { return important3; }
};

您仍然可以通过将其设置为某种单例来改进此类(因为您希望计算只进行一次)。

【讨论】:

    【解决方案3】:

    您可以将const 字段移动到基类,然后传递一个包装类来初始化它们:

    class MyBase
    {
    protected:
        const double ImportantConstant1;
        const double ImportantConstant2;
        const double ImportantConstant3;
    
        struct Initializer
        {
            double d1;
            double d2;
            double d3;
        };
    
        MyBase(Initializer const& i):
            ImportantConstant1(i.d1),ImportantConstant2(i.d2),ImportantConstant3(i.d3)
        {}
    };
    
    class MyReferenceClass:
        private MyBase
    {
    public:
        using MyBase::ImportantConstant1;
        using MyBase::ImportantConstant2;
        using MyBase::ImportantConstant3;
        MyReferenceClass():
            MyBase(makeInitializer())
        {}
    
    private:
        MyBase::Initializer makeInitializer()
        {
            MyBase::Initializer i;
            ComputeImportantConstants(&i.d1,&i.d2,&i.d3);
            return i;
        }
    
        void ComputeImportantConstants(double *out_const1, double *out_const2, double *out_const3);
    };
    

    【讨论】:

    • 效果很好;只需要更改为公共 MyBase 继承并使 const 双字段公开。此外,在实际代码中,这三个值已经在一个结构中传递,所以我不需要额外的 Initializer 结构。 (我真的应该用那个结构写我的问题......)
    【解决方案4】:

    我发现初始化 const 字段的唯一方法是使用初始化列表,但似乎无法在这样的列表中传递多输出计算的结果。

    确实如此;但是,您可以初始化单个成员 - 这是一个常量结构。见下文。

    我也考虑过将 ImportantConstant1..3 设为非常量字段或函数调用,但两者似乎都逊色。

    我不认为 getter 函数会逊色。编译器很可能会内联它们。考虑一下:

    class MyReferenceClass
    {
    public:
        MyReferenceClass() : m_constants( ComputeImportantConstants() ) { }
    
        inline double ImportantConstant1() const { return m_constants.c1; }
        inline double ImportantConstant2() const { return m_constants.c2; }
        inline double ImportantConstant3() const { return m_constants.c3; }
    
    private:
        struct Constants {
            Constants( double c1_, double c2_, double c3_ ) : c1( c1_ ), c2( c2_ ), c3( c3_ ) { }
    
            const double c1;
            const double c2;
            const double c3;
        };
    
        Constants ComputeImportantConstants() {
            return Constants( 1.0, 2.0, 3.0 );
        }
    
        const Constants m_constants;
    };
    

    由于m_constants 及其所有字段都是常量,因此其他成员方法无法更改这些值 - 只需在您在问题中草绘的代码中即可。初始化可以是 在这里使用,因为我们初始化了一个值:一个结构。

    对常量的访问(很可能)和以前一样高效:建议内联函数,编译器很可能会这样做,因为 getter 很小。

    【讨论】:

      【解决方案5】:

      要修改已接受的答案,请注意,从 C++11 开始,您可以使用非常巧妙的技巧。例如,您的原始问题可以通过 lambda 和构造委托解决,如下所示:

      class MyReferenceClass {
      
      public: /* Methods: */
      
          MyReferenceClass()
              : MyReferenceClass([](){
                      std::array<double, 3u> cs; /* Helper class, array or tuple */
                      computeImportantConstants(&cs[0u], &cs[1u], &cs[2u]);
                      return cs;
                  })
          {}
      
          const double importantConstant1;
          const double importantConstant2;
          const double importantConstant3;
      
      private: /* Methods: */
      
          MyReferenceClass(std::array<double, 3u> constants)
              : ImportantConstant1(constants[0u])
              , ImportantConstant2(constants[1u])
              , ImportantConstant3(constants[2u])
          {}
      
          static void computeImportantConstants(double * out_const1,
                                                double * out_const2,
                                                double * out_const3);
      
      }; /* class MyReferenceClass { */
      

      或者更好的是,如果可能,将初始化代码从 computeImportantConstants 移到构造函数中的 lambda 中。

      在实践中,使用 lambda 调用来初始化常量成员是一个非常方便的技巧,尤其是因为您还可以绑定和/或将参数传递给 lambda。使用构造委托有助于简化成员的初始化,这些成员最好一起初始化或可能相互依赖。

      但是,在使用构造委托时要格外小心,因为函数调用(或构造函数调用)的函数参数的初始化顺序是未定义的,最终可能会以不正确的顺序或以不正确的方式初始化事物如果发生故障或抛出异常,可能会导致资源泄漏。

      【讨论】:

        【解决方案6】:

        只需将事物拆分为初始化简单的部分和复杂的部分,然后通过复制构造函数初始化复杂的部分:

        // here's the part with the consts: 
        struct ComplexPart
        {
            const double a,b,c; 
            ComplexPart(double _a, double _b, double _c) {}
        };
        // here's the expensive calc function:
        void calc(double *a,double *b,double *c);
        
        // and this is a helper which returns an initialized ComplexPart from the computation:
        ComplexPart calc2()
        {
            double *a,*b,*c;
            calc(&a,&b,&b);
            return ComplexPart(a,b,c);
        }
        // put everything together:    
        struct MyReferenceClass : public ComplexPart
        {
            MyReferenceClass() : ComplexPart(calc2()) {}
        };
        

        【讨论】:

          【解决方案7】:

          这样的事情呢:

          class A
          {
            private:
              static void calc(double &d1, double &d2, double &d3)
              {
                d1 = 1.0;
                d2 = 2.0;
                d3 = 3.0;
              }
              class D
              {
                public:
                  operator double() const
                  {
                    return(x);
                  }
                private:
                  friend class A;
                  double x;
              };
            public:
              A()
              {
                calc(d1.x, d2.x, d3.x);
              }
              D d1, d2, d3;
          };
          
          #include <iostream>
          
          int main()
          {
            A a;
            std::cout << a.d1 << std::endl;
            std::cout << a.d2 << std::endl;
            std::cout << a.d3 << std::endl;
            // the following lines will not compile so you can't change the value
            // std::cout << a.d3.x << std::endl;
            // a.d2.x = 0.0;
            return(0);
          }
          

          【讨论】:

            【解决方案8】:

            上面的答案似乎都没有注意一个细节:这里提到了static,所以这些常量似乎与类的实际实例无关。

            换句话说:这些是全局常量。如您所料,const 关键字的存在在这里很重要,因为编译器将应用优化。

            无论如何,这个想法是使用辅助结构。

            // foo.h
            class Foo
            {
            public:
              static double const m1;
              static double const m2;
              static double const m3;
            };
            
            // foo.cpp
            struct Helper
            {
              double m1, m2, m3;
              Helper() { complexInit(m1, m2, m3); }
            } gHelper;
            
            double const Foo::m1 = gHelper.m1;
            double const Foo::m2 = gHelper.m2;
            double const Foo::m3 = gHelper.m3;
            

            当然,在一个真正的程序中,我鼓励你将常量实际包装在某种接口后面,以这种方式公开它们是非常糟糕的做法,因为它使更改它们(使用另一种类型)变得非常困难。

            还要注意,输出参数不需要指针,普通引用就可以。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2020-02-15
              相关资源
              最近更新 更多