Комплексная инициализация константных полей

Рассмотрим класс, подобный этому:

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 класса?

Если нет, можете ли вы предложить более естественный способ объявить такой класс в С++?

В С# я бы использовал статический класс со статическим конструктором здесь, но это не вариант в С++. Я также считал, что делать ImportantConstant1..3 либо неконстантными полями, либо вызовами функций, но оба кажутся низшими.

Единственный способ инициализировать константные поля, которые я нашел, это использовать списки инициализаций, но не представляется возможным передать результаты вычисления с несколькими выходами в таком списке.

Ответ 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;
};

Итак, и функция расчета превратится в функцию factory?

Ответ 2

сначала - вы можете сделать зло: отбросьте const в ComputeImportantConstants() и поместите туда значения. Не делайте этого, потому что тогда вы лжете компилятору, и он попытается найти самый противный способ вернуть деньги.

второй:

сделайте что-нибудь вроде этого:

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

Ответ 4

Единственный способ инициализировать константные поля, которые я нашел, - использовать списки инициализаторов, но не представляется возможным передать результаты вычисления с несколькими выходами в таком списке.

Это правда; однако вы можете инициализировать один элемент, который является структурой констант. См. Ниже.

Я также рассматривал возможность делать 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, а также все его поля являются постоянными, значения не могут быть изменены другими методами-членами - просто в коде, который вы набросали в своем вопросе. Инициализация может быть  используется здесь, поскольку мы инициализируем одно значение: struct.

Доступ к константам (скорее всего) будет таким же эффективным, как и раньше: предложение встроить функции и компилятор, скорее всего, сделает это, учитывая, насколько малы получатели.

Ответ 5

Чтобы изменить принятый ответ, обратите внимание, что с С++ 11 вы можете делать очень аккуратные трюки. Например, ваша оригинальная проблема может быть решена с помощью лямбда-команды и строительной делегации следующим образом:

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 в конструкторе, если это возможно.

На практике использование лямбда-вызовов для инициализации постоянных членов - очень удобный трюк, особенно потому, что вы также можете связывать и/или передавать аргументы в лямбда. И использование делегирования строительства помогает облегчить инициализацию членов, которые лучше всего могут быть инициализированы вместе или могут зависеть друг от друга.

Однако проявляйте особую осторожность при использовании делегирования конструкции, поскольку порядок инициализации аргументов функции для вызова функции (или вызова конструктора) равен undefined, и можно завершить инициализацию вещей в неправильном порядке или в способ, который может привести к утечке ресурсов, если что-то не сработает или выбрасывает исключение.

Ответ 6

Просто разделите вещь на часть, которую просто инициализировать, и сложную часть, и инициализируйте сложную часть через конструктор копирования:

// here the part with the consts: 
struct ComplexPart
{
    const double a,b,c; 
    ComplexPart(double _a, double _b, double _c) {}
};
// here 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;

Конечно, в реальной программе я бы посоветовал вам фактически обернуть константы за каким-то интерфейсом, это очень плохая практика, чтобы разоблачить их таким образом, потому что это очень сложно изменить их (используя другой тип).

Также обратите внимание, что вам не нужны указатели для выходных параметров, простые ссылки делают.