Complex initialization of const fields
Asked Answered
B

8

12

Consider a class like this one:

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

There is a routine (ComputeImportantConstants) that computes three constants at runtime. Suppose the computation is fairly complex, and inherently produces all three values at once. Moreover, the results depend on build configuration, so hardcoding the results isn't an option.

Is there a sensible way to store these computed values in the corresponding const double fields of the class?

If not, can you suggest a more natural way to declare such a class in C++?

In C# I would use a static class with a static constructor here, but that isn't an option in C++. I have also considered making ImportantConstant1..3 either non-const fields or function calls, but both seem inferior.

The only way to initialize const fields that I found is to use initializer lists, but it doesn't seem possible to pass the results of a multi-output computation in such a list.

Blow answered 15/7, 2010 at 13:56 Comment(1)
If it's possible, can you tell how ComputeImportantConstants is implemented? Is it rather long? How do the three constants interact, what other factors are involved?Sallysallyann
F
10

Why can't you do:

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

Like that and have the calculate function turn into a factory function?

Foolish answered 15/7, 2010 at 14:1 Comment(5)
one improvement idea: make one static variable in ComputeImportantConstants() and return this variable once everything is computed. This way, subsequent calls of ComputeImportantConstants do not trigger an additional computation.Insatiable
C++ nitpick: returning a const double from a function doesn't make much sense. It only makes life harder than necessary for the caller without improving security. The member variables are returned by value, after all.Sallysallyann
There is nothing wrong with a struct having no methods and only holding data (POD types.) On the other hand, it's often far easier to affect change within a program if what is being accessed is hidden behind a function call. Most people are going to prefer encapsulation because of the latter.Foolish
The nitpick is wrong - there's no const double returned, only a double is returned by a const method, i.e. a method that guarantees not to change the content.Tertian
@Chris: If you look at the change history you will see that about two years ago there was a const double. ;-)Sallysallyann
I
5

first - you can do evil: cast away const in ComputeImportantConstants() and place the values there. Don't do it though, because then you lie to the compiler and it'll try to find the nastiest way to pay back.

second:

do something like this:

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

you still can improve this class by making it some kind of singleton or so (since you want the computation to be done only once).

Insatiable answered 15/7, 2010 at 14:4 Comment(0)
L
3

You could move the const fields to a base class, and then pass an wrapper class to initialize them:

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);
};
Lashawnda answered 15/7, 2010 at 14:21 Comment(1)
Works nicely; just needed to change to public MyBase inheritance and make the const double fields public. Also, in the real code the three values are already passed around in a struct so I don't need an extra Initializer struct. (I should really have written my question using that struct...)Blow
S
2

The only way to initialize const fields that I found is to use initializer lists, but it doesn't seem possible to pass the results of a multi-output computation in such a list.

That's true; however, you could initialize a single member - which is a struct of constants. See below.

I have also considered making ImportantConstant1..3 either non-const fields or function calls, but both seem inferior.

I don't think that getter functions would be inferior. The compiler would most likely inline them. Consider this:

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

Since m_constants as well as all its fields are constant, the values cannot be changed by other member methods - just in the code you sketched in your question. An initialize can be used here since we initialize a single value: a struct.

Access to the constants is (most likely) to be as efficient as before: the suggest to inline the functions and the compiler is quite likely to do so given how small the getters are.

Sallysallyann answered 15/7, 2010 at 14:23 Comment(0)
E
2

To amend the accepted answer, please note, that as of C++11 you can do very neat tricks. For example, your original problem can be solved with a lambda and construction delegation as follows:

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 { */

Or better yet, move the initialization code from computeImportantConstants into the lambda in the constructor, if possible.

In practice, using lambda calls to initialize constant members is a very handy trick, especially because you can also bind and/or pass arguments to the lambda. And using construction delegation helps to ease initialization of members which can best be initialized together or might depend on each another.

However, exercise extra caution when using construction delegation, because the initialization order of function arguments for a function call (or a constructor call) is undefined, and one might end up initializing things in an incorrect order, or in a manner which could lead to resource leaks if something fails or throws an exception.

Elate answered 11/3, 2016 at 15:34 Comment(0)
V
1

Just split up the thing into the part that is simple to initialize and the complex part, and initialize the complex part via copy constructor:

// 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()) {}
};
Ventilator answered 15/7, 2010 at 14:31 Comment(0)
M
1

What about something like that:

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);
}
Mccreary answered 15/7, 2010 at 14:46 Comment(0)
J
1

None of the answer above seemed to pay attention to a detail: static is mentioned here, so these constants seem to be independent of the actual instance of the class.

In other words: those are global constants. As you guessed, the presence of the const keyword is important here, because of the optimizations the compiler will apply.

Anyway, the idea is to use a helper structure.

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

Of course, in a real program, i would encourage you to actually wrap the constants behind some kind of interface, it's really bad practice to expose them this way, because it makes changing them (using another type) very difficult.

Also note that you don't need pointers for output parameters, plain references do.

Jobholder answered 15/7, 2010 at 15:37 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.