Complex initialization of const fields
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开发者_JAVA百科 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.
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?
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).
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);
};
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.
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.
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()) {}
};
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);
}
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.
精彩评论