Комплексная инициализация константных полей - PullRequest
11 голосов
/ 15 июля 2010

Рассмотрим такой класс:

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 либо неконстантными полями, либо вызовами функций, но оба они кажутся неполноценными.

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

Ответы [ 8 ]

9 голосов
/ 15 июля 2010

Почему ты не можешь сделать:

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

Вот так и есть ли функция вычисления, превращающаяся в фабричную функцию?

5 голосов
/ 15 июля 2010

сначала - вы можете сделать зло: отбросьте 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 голосов
/ 15 июля 2010

Вы можете переместить поля 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);
};
2 голосов
/ 11 марта 2016

Чтобы изменить принятый ответ, обратите внимание, что начиная с C ++ 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 в лямбду в конструкторе, если это возможно.

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

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

2 голосов
/ 15 июля 2010

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

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

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

Я не думаю, что функции получателя будут хуже. Компилятор, скорее всего, встроит их. Учтите это:

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

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

1 голос
/ 15 июля 2010

Ни один из приведенных выше ответов, по-видимому, не обращал внимания на детали: здесь упоминается 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;

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

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

1 голос
/ 15 июля 2010

Как насчет чего-то такого:

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);
}
1 голос
/ 15 июля 2010

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

// 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()) {}
};
...