代码之家  ›  专栏  ›  技术社区  ›  Roman Starkov

常量字段的复杂初始化

  •  11
  • Roman Starkov  · 技术社区  · 15 年前

    考虑这样一个类:

    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双字段中?

    如果不是,你能建议一种更自然的方式来在C++中声明这样的类吗?

    在C++中,我会在这里使用静态构造函数的静态类,但这不是C++中的选项。我也考虑过使importantconstant1..3成为非常量字段或函数调用,但这两个调用看起来都不好。

    初始化常量字段的唯一方法是 use initializer lists 但似乎不可能在这样的列表中传递多输出计算的结果。

    8 回复  |  直到 9 年前
        1
  •  9
  •   wheaties    15 年前

    为什么你不能这样做:

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

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

        2
  •  5
  •   Tobias Langner    15 年前

    首先,你可以做坏事:在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
  •  3
  •   Anthony Williams    15 年前

    你可以移动 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);
    };
    
        4
  •  2
  •   Frerich Raabe    15 年前

    我找到的初始化常量字段的唯一方法是使用初始值设定项列表,但似乎不可能在这样的列表中传递多输出计算的结果。

    这是正确的;但是,您可以初始化一个单独的成员——它是一个常量结构。见下文。

    我也考虑过使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
  •  2
  •   jotik    9 年前

    为了修正被接受的答案,请注意,对于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
  •  1
  •   Nordic Mainframe    15 年前

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

    // 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
  •  1
  •   Tomek    15 年前

    像这样的情况怎么样:

    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
  •  1
  •   Matthieu M.    15 年前

    上面的回答似乎都没有注意到一个细节: 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;
    

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

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