C++: automatic initialization
I find it sometimes annoying that I have to initialise all POD-types manually. E.g.
struct A {
int x;
/* other stuff ... */
A() : x(0) /*...开发者_如何学JAVA*/ {}
A(/*..*/) : x(0) /*...*/ {}
};
I don't like this for several reasons:
- I have to redo this in every constructor.
- The initial value is at a different place than the variable declaration.
- Sometimes the only reason I have to implement a constructor is because of this.
To overcome this, I try to use my own types instead. I.e. instead of using int x,y;
, I use my own vector struct which also initialize automatically with 0
. I also thought about just implementing some simple wrapper types, like:
template<typename T>
struct Num {
T num;
Num() : num(0) {}
operator T&() { return num; }
operator const T&() const { return num; }
T& operator=(T _n) { num = _n; return num; }
/* and all the other operators ... */
};
This basically solves this so far for all cases where I want to init with 0
(that are by far the most often cases for me).
Thanks to James McNellis for the hint: This can also be solved via the boost::value_initialized
.
Now, not limited to POD-types:
But sometimes I want to initialise with something different and there are the troubles again because that Num
template struct cannot easily be extended to allow that. Basically because I cannot pass floating point numbers (e.g. float
) as a template parameter.
In Java, I would just do:
class A {
int x = 42;
/*...*/
public A() {}
public A(/*...*/) { /*...*/ }
public A(/*...*/) { /*...*/ }
/*...*/
}
I find it quite important that in such cases where you want to init a member variable always in the same way in all possible constructors, that you are able to write the init value directly next to the member variable, like in int x = 42;
.
So the thing I was trying to solve is to do the same thing in C++.
To overcome the problem that I cannot pass the init-value via a template parameter, I hacked together an ugly macro:
#define _LINENAME_CAT( name, line ) name##line
#define _LINENAME( name, line ) _LINENAME_CAT( name, line )
/* HACK: use _LINENAME, workaround for a buggy MSVC compiler (http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=360628)*/
#define PIVar(T, def) \
struct _LINENAME(__predef, __LINE__) { \
typedef T type; \
template<typename _T> \
struct Data { \
_T var; \
Data() : var(def) {} \
}; \
Data<T> data; \
T& operator=(const T& d) { return data.var = d; } \
operator const T&() const { return data.var; } \
operator T&() { return data.var; } \
}
(For other compilers, I can just omit that _LINENAME
name for the struct and just leave it unnamed. But MSVC doesn't like that.)
This now works more or less like I want it. Now it would look like:
struct A {
PIVar(int,42) x;
/*...*/
A() {}
A(/*...*/) { /*...*/ }
A(/*...*/) { /*...*/ }
/*...*/
};
While it does what I want (mostly), I still am not fully happy with it:
- I don't like the name
PIVar
(which stands forPreInitVar
) but I really couldn't come up with something better. At the same time, I want to have it short. - I don't like that macro hack.
How have you solved this? Any better solution?
There was an answer which was deleted again which said that C++0x allows basically the same syntax as in Java. Is that true? So then I would just have to wait for C++0x.
Please don't give any comments like:
- "then just use Java instead" / "don't use C++ then" or
- "if you need something like this, you are probably doing something wrong" or
- "just don't do it this way".
Also, please don't tell me not to use it. I know about all the drawbacks of my current solution. Please only make comments about non-obvious drawbacks if you are really sure that I am not aware of that. Please don't just state that there are many drawbacks in my current solution. Please also don't state that it is not worse to use it. I am just asking if you know about a better solution than the one I have presented here.
Sometimes the only reason I have to implement a constructor is because of this.
You don't have to do that.
struct POD {
int i;
char ch;
};
POD uninitialized;
POD initialized = POD();
Equally in an initialization list:
class myclass
POD pod_;
// ....
myclass()
: pod_() // pod_'s members will be initialized
{
}
To overcome this, I try to use my own types instead.
Your type fails in this scenario:
void f(int&);
Num<int> i;
f(i);
There's likely more problems, but this is what occurred to me immediately.
How have you solved this? Any better solution?
Yes, we all have solved this. We did by not attempting to fight the language, but to use it the way it was created: initialize PODs in initialization lists. When I see this:
struct ML_LieroX : MapLoad { std::string id; PIVar(int, 0) type; std::string themeName; PIVar(int, 0) numObj; PIVar(bool,false) isCTF;
I cringe. What is this doing? Why is it this way? Is this even C++?
All this just to save a few keystrokes typing an initialization list? Are you even serious?
Here's an old bon mot: A piece of code gets written once, but over its lifetime will be read tens, hundreds, or even thousands of times. That means that, in the long run, the time it takes to write a piece code is more or less neglectable. Even if it takes you ten times as long to write the proper constructors, but it saves me 10% of the time necessary to understand your code, then writing the constructors is what you should do.
Boost provides a value_initialized<T>
template that can be used to ensure an object (POD or not) is value-initialized. Its documentation goes into great detail explaining the pros and cons of using it.
Your complaint about not being able to automatically initialize an object to a given value doesn't make much sense; that has nothing to do with the object being POD; if you want to initialize a non-POD type with a non-default value, you have to specify the value when you initialize it.
You could initialize POD structures as follows:
struct POD
{
int x;
float y;
};
int main()
{
POD a = {}; // initialized with zeroes
POD b = { 1, 5.0f }; // x = 1, y = 5.0f
return 0;
}
There is a proposal for C++0x which allows this:
struct A {
int x = 42;
};
That is exactly what I want.
If this proposal is not making it into the final version, the possibility of delegating constructors is another way of at least avoiding to recode the initialization in every single constructor (and at the same time avoiding a dummy helper function to do this).
In current C++, there does not seem to be any better way to do it despite what I have already demonstrated.
C++ does have constructor delegation, so why not use it?
struct AState
{
int x;
AState() : x(42) {}
};
class A : AState
{
A() {}
A(/*...*/) { /*...*/ }
A(/*...*/) { /*...*/ }
};
Now initialization of x
is delegated by all constructors. The base constructor can even accept arguments passed from each version of A::A
.
Prior to C++0x there is a solution which works well if the non-zero value you want to initialize with is not completely arbitrary (which is usually the case in practice). Similar to boost::initialized_value but with an extra argument to take the initial value (which gets a little fussy because C++).
template<typename T> struct Default { T operator()() { return T(); } };
template<typename T, T (*F)()> struct Call { T operator()() { return F(); } };
template<int N> struct Integer { int operator()() { return N; } };
template< typename X, typename Value = Default<X> >
class initialized {
public:
initialized() : x(Value()()) {}
initialized(const X& x_) : x(x_) {}
const X& get() const { return x; }
operator const X&() const { return x; }
operator X&() { return x; }
private:
X x;
};
You might use it like this:
struct Pi { double operator()() { return 3.14; } }; //Exactly
const char* init_message() { return "initial message"; }
Point top_middle() { return Point(screen_width()/2, 0); }
struct X {
initialized<int> a;
initialized<int, Integer<42> > b;
initialized<double> c;
initialized<double, Pi> d;
initialized<std::string> e;
initialized<std::string, Call<const char*, init_message> > f;
initialized<Point> g;
initialized<Point, Call<Point,top_middle> > h;
X() {}
};
I find the annoyance of having to create a dummy function to return any non-integral / non-default value is generally amortized across the entire library (since the non-zero initial values for a particular type are generally shared by many classes).
Obviously typedef is a friend here.
Anyway, can't wait to upgrade to C++0x/11/14/whatever.
精彩评论