Re: Help in understanding initialization of data members of class
(esp. static)
On Wednesday, 3 April 2013 04:03:15 UTC+1, Shriramana Sharma wrote:
Hello. I have been trying to understand how the initialization
of data members of a class happens, esp. static members.
I should note that I am using GCC 4.6.3 (64 bit) that came
with Kubuntu Precise for my testing, so the error messages
I inline in comments are from that. And all of the errors are
compiler errors except for one which I have marked as a linker
error.
I have inlined my queries in comments with three slashes: ///
-- I would much appreciate clarifications to those queries.
struct a {
a () {}
// int i = 1 ;
// static int ii = 2 ;
// const int iii = 3 ;
/// Q: Why does ISO C++ forbid initialization within the class
/// definition of all three above but not the below?
static const int iiii = 4 ;
} ;
Historically, you couldn't initialize anything in the class
definition. For non static members, there isn't anything to
initialize until an instance of the object was created. For
static members, the declaration in the class is just that,
a declaration, and not a definition. And you only initialize in
definitions.
This led to a problem, however. People wanted to write things
like:
struct A
{
static int const n = 42;
double array[n];
};
The dimension of the array must be a compile time constant;
a non-static couldn't work (because even if const, it might be
different from one instance to the other), but otherwise, an
`int const` is fine *provided* the compiler can see the
initialization value. So the committee made a special exception
to the rule that declarations which aren't definitions cannot
have an initializer. IIRC, this was fairly late in the process,
just before the standard was adopted; the idea was to add as
little as possible to the standard at that point. And the
compile time constants like this are only needed for integral
constants. So the rule was that you can provide such an
initializer *only* if three conditions were met: the member
variable is const, the member variable is static, and the member
variable has integral type.
We now have concrete experience with this, and have had time to
consider all of the implications. The result is that in C++11,
you can provide the initializer for all member variables. (But
the actual meaning is still subtily different for static and for
non-static.)
struct b {
b () {}
int i ;
// int & ir = i ; // "i cannot appear in constant expr", "iso c++ forbids initialization of member"
// int * ip = &i ; // "i cannot appear in constant expr", "& cannot appear in constant expr"
/// Q: I get that without an object (together with its
/// members) actually being created I can't produce a reference to
/// it or take its address. (it = either object or member). But
/// I don't get how this is a constant expr.
} ;
Again, the original motivation for allowing this syntax was to
allow const static member variables of integral type to be used
in "constant integral expressions". The requirement for the
initialization is thus that the initialization expression itself
be a constant integral expression.
I've not verified in C++11, but I suspect that the requirement
that the initialization expression be const is still present.
I can't quite see how you could implement it otherwise.
(Remember, the expression initializes an object which will be
defined elsewhere.)
struct c {
c () : ir ( i ), ip ( &i ) {}
int & ir ; int * ip ; int i ;
/// Q: Given that I am declaring i after ir and ip, how is it
/// that I can initialize ir and ip before i?
} ;
You can't. It only looks like you can. Regardless of the order
you write the initializers, initialization will occur in the
order the members are declared in the class.
Good compilers warn when your initialization list doesn't
correspond to this, because it is confusing otherwise---the code
is executed in a different order than what you wrote.
struct d {
d () /*: i ( 1 )*/ {} // "can only be initialized at definition"
/// Q: Why is this an error? I can do i=1 within the {} and it
/// is not wrong, but it is wrong to do it before the {} ?
static int i ;
} ;
Initialization lists initialize the object being constructed.
Static members aren't part of the object. The static member
will be created when the program is loaded.
struct e {
e () {}
static const int i = 1 ;
static const double ii = 1.1 ;
// static const int * const ip = &i ; // "invalid in-class initialization of static member of non-integral type"
/// Q: I don't get it why I can't take address of static
/// member in the class definition. Perhaps because the static
/// object is only created after the entire definition is read?
/// Why would that be so? And when I am able to initialize
/// a static double, why does it say "non-integral type"?
} ;
Again, the problem isn't taking the address of a static member.
It's that you're trying to initialize a non-integral type.
struct f {
f () {}
static const int i = 1 ;
// const int * const ip = &i ; // "& cannot appear in constant expr", "iso c++ forbids initialization of member"
/// Q: Um, I guess I can't use & because as in struct e above,
/// the member i is only actually created after reading the entire
/// definition. But the error messages don't reflect that clearly.
/// Why?
} ;
The most immediate answer is that error messages aren't always
that clear. You are getting two error messages, however, for
two distinct problems: the second is that you're trying to
initialize a static member which doesn't have integral type.
The first is due to the fact that there are serious restrictions
on what you can do with addresses (even constant addresses) in
constant expressions (and the initialization must be a constant
expression). In reality, although the address may formally be
a constant, the compiler doesn't know what it is; the actual
address won't be determined until link time. So addresses
cannot generally occur in "integral constant expressions".
# ifdef TEST1
struct g {
static const int i = 5 ;
static const int * const ip ;
} ;
const int * const g::ip = &i ; // LINKER: undefined reference to 'g::i'
# endif
As I said above, the declaration of the static member in the
class is *not* a definition. The rule remains that if an object
is used, it must be defined somewhere. (It is the definition
which causes the compiler to allocate space for it, and give it
an address.)
struct h {
static const int i ;
static const int * const ip ;
} ;
const int h::i = 0 ;
const int * const h::ip = &i ;
/// Q: I expect this class to be identical to the above, with
the only exception being that I have initialized i at
namespace scope. The above doesn't work but this works? The
standard (I'm reading the last public draft of C++11) seems to
say at 9.4.2.3 that "the member shall still be defined in
namespace scope if it is odr-used in the program". I wonder if
this applies in the present case. And I am unable to
understand the lengthy definition of "odr-used" so I would
appreciate a brief explanation if possible.
You've found the clause which explains it. (Although from what
has preceded, I gather that your compiler doesn't support C++11.
Most don't, at least not completely.) The declarations of
static data members in the class itself are *not* definitions,
and you have to provide a definition. (*One* definition, in
a single translation unit, and not in a header.)
struct j {
static const int * const ip ;
} ;
const int * const j::ip = new int ;
# ifdef TEST2
struct k {
static const int * const ip = new int ; // "'new' cannot appear in a constant expr", "invalid in-class initialization of static member of non-integral type"
/// Q: Again, I'm not sure why this is a constant expr, why
/// new can't appear in it, and why it says "non-integral" type.
} ;
# endif
Same issues as above.
--
James