Help in understanding initialization of data members of class (esp. static)

  • Thread starter Shriramana Sharma
  • Start date
S

Shriramana Sharma

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. Thank you very much in advance!

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

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

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

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

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

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 iis only actually created after reading the entire definition. But the error messages don't reflect that clearly. Why?
} ;

# 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

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'twork 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 inthe present case. And I am unable to understand the lengthy definition of "odr-used" so I would appreciate a brief explanation if possible.

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

int main () {
delete j::ip ;
# ifdef TEST2
delete k::ip ;
# endif
}
 
S

Stefan Ram

It seems that this requirement was relaxed in C++

Deviating from the topic of the question:

My opinion is that a new language needs a new name.
So I'd suggest to call this new language »C++11«.

The ISO, however, has chosen to call it »C++«. So I
wrote »C++« above.

The ISO has not given a new name to the language that
formerly was called »C++«. As far as ISO is concerned
it does not exist anymore, even though it is used
more than C++. To the ISO, the old language specification
is »canceled and replaced«, that is, null and void.

In the usenet, all other posters (except me) agreed
with ISO that a new language should reuse an existing
name of another language. But inconsistently they use
»C++11« to name what the ISO calls »C++«. So their
behavior does not correspond with their declarations.

This inconsistency is a necessary consequence of the
false idea to use time-dependent identifiers to refer
to entities of the time-independent realm of formal
languages (which, essentially, are mathematical sets).
Sets do not change.

As many inconsistencies in the computer industry
(Java 1.7 being called »Java 7«) it might be related
to marketing. If the new C++ would have been given a
totally new name, it might not become as successful as
a new language being called »C++«.

»C++11 feels like a new language.«

http://www.stroustrup.com/C++11FAQ.html
 
8

88888 Dihedral

Stefan Ram? 2013?4?3????UTC+8??12?07?56????
Deviating from the topic of the question:



My opinion is that a new language needs a new name.

So I'd suggest to call this new language »C++11«.



The ISO, however, has chosen to call it »C++«. So I

wrote »C++« above.



The ISO has not given a new name to the language that

formerly was called »C++«. As far as ISO is concerned

it does not exist anymore, even though it is used

more than C++. To the ISO, the old language specification

is »canceled and replaced«, that is, null and void.



In the usenet, all other posters (except me) agreed

with ISO that a new language should reuse an existing

name of another language. But inconsistently they use

»C++11« to name what the ISO calls »C++«. So their

behavior does not correspond with their declarations.



This inconsistency is a necessary consequence of the

false idea to use time-dependent identifiers to refer

to entities of the time-independent realm of formal

languages (which, essentially, are mathematical sets).

Sets do not change.



As many inconsistencies in the computer industry

(Java 1.7 being called »Java 7«) it might be related

to marketing. If the new C++ would have been given a

totally new name, it might not become as successful as

a new language being called »C++«.



»C++11 feels like a new language.«



http://www.stroustrup.com/C++11FAQ.html
Maybe C++11 is aimed to gain acceptance from the US defense
department.

C++ was not allowed before to be used in the military projects.
 
J

James Kanze

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.
 
B

Bart van Ingen Schenau

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. Thank you very much in
advance!

As a note upfront, significant changes have been made in this area with
the introduction of the new C++11 standard and some of the thing your
compiler complains about are allowed under the new standard.
In my comments, I will explain how the rules are under the old C++98/C+
+03 standard, as those are the rules that your compiler uses.

In C++03, in-class initialization of data members is not allowed, with
only one exception: static const integral members.
For non-static members, in-class initialization was deemed to be
unnecessary because those members should be initialized in a constructor
(this view has been abandoned in C++11, where limited in-class
initialization of non-static data members is allowed).
For static members the problem is that they are shared between all
instances of the class, so there should be only one copy of the static
member. So, how do we ensure that memory for that static member gets set
aside only once? It can't be triggered by the member declaration in the
class itself, because that class can be defined in dozens of different
source files. So, you are required to provide one out-of-class definition
of a static member. And to ensure that the compiler knows at that point
exactly with which value to initialize that static member, it was ruled
that the initializer must be specified only on the out-of-class
definition.
Now we come to the single exception. The problem with requiring that the
initializer for static members is only on the out-of-class definition is
that you then can't use static members to provide a named constant for,
for example, array sizes, because the compiler would have to see the
definition to know how large the array should be. So, for static const
integer members an exception was made to allow in-class initializers so
that those members could still be used to specify the size of an array.
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 ;
} ;

See above.
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.

A constant expression is an expression that can be completely evaluated
by the compiler into a single constant value.
The member b::i does not qualify, because its value is not known at
compile time.
The address-of operator can't be used, because the compiler generally
does not know where something will get placed in memory when the
application is executed.
} ;

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

The memory for all the members of struct c gets allocated in one go
before the constructor is executed. This means that although c::i has not
yet been initialized, its address is known and can be used to initialize
other members, like c::ir and c::ip. That the value of c::i has not yet
been determined is no problem, because its value is not needed for those
initializations.
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 ;
} ;

Every variable can only be initialized once (but can be assigned as often
as you want). Because d::i is declared as static, all instances of struct
d share the same copy of d::i. This means that if you could initialize
d::i in the constructor of d, you would be able to initialize d::i
multiple times, which goes against one of the fundamental principles of C+
+.
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"?

The in-class initialization of e::ii should have been flagged as an
error. You might have encountered a GCC extension here.
For the rest, see above.
} ;

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?

Producing high-quality error messages is one of the hardest things to do
for a compiler because it essentially requires the compiler to understand
what you meant. Often that is even hard to do for humans, let alone for a
machine.
} ;

# 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'

Even though in-class initialization of static const integer members is
allowed, it is not seen as a definition. So, if you want to use the
address of g::i, you still need an out-of-class definition for it.
# endif

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.

Yes that applies in this case.
The standard has to be very precise in its specifications and that makes
for lengthy definitions.
In loose summary, a variable is odr-used if it must exist somewhere in
memory during the execution of the program. A function is odr-used if it
might be called during the execution of the program.
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.

First, a pointer is a non-integral type because, simply, a pointer is not
an integer.
Secondly, as explained above, a constant expression must have the
property that it can be fully evaluated by the compiler and the compiler
can't know which piece of memory new will be allocating here.
} ;
# endif

int main () {
delete j::ip ;
# ifdef TEST2
delete k::ip ;
# endif
}

Bart v Ingen Schenau
 
S

Shriramana Sharma

Hello people and thank you very much for your kind explanations. It has indeed helped me to understand the meaning of static and other things very well. And thankfully I was able to test some on GCC 4.7.2 on a separate install which supports the members inline initialization feature of C++11 and better still I came to actually try clang (I had heard about it but never tried) which seems really user-friendly esp. in terms of legible error messagesand the clang 3.0 on my main working install directly supports this feature of C++11.

Now my further questions, esp related to C++11's new syntax re these matters: code and comments as above, except that my own comments (which I requestyou to confirm or correct if wrong) are prefixed by C and questions by Q:


struct e {
e () {}
static const int i = 1 ;
/// C: Even in C++11 you cannot initialize non-integral types as static andconst because static const was only ever intended for array sizes and the like; in C++11 use constexpr static. This seems to be a more generic thing.
// static const double ii = 1.1 ;
// static const int * const ip = &i ;
constexpr static double ii = 1.1 ;
constexpr static const int * ip = &i ;
} ;

struct f {
f () {}
static const int i = 1 ;
const int * const ip = &i ;
/// C: ip is not static and hence initialized only at class instantiation so it's ok and no need for constexpr
} ;

# 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

/// C: No memory is actually allocated for inline-defined static const objects (which are also constrained to be of integral types) -- so you can't take the address of such an object -- if you want to do that either don't inline the definition, or define it as constexpr. Though g above is not valid,either h1 or h2 below are valid.
/// Q: Does C++11 specifically say that constexpr will allocate memory? I tried reading the standard but again failed to parse the intricate definitions. OTOH http://www.stroustrup.com/C++11FAQ.html#constexpr actually seems to say that the compiler only holds constexpr values in its tables which seems to be the exact opposite.

struct h1 {
static const int i ;
static const int * const ip ;
} ;
const int h1::i = 0 ;
const int * const h1::ip = &i ;

struct h2 {
constexpr static int i = 5 ;
constexpr static const int * ip = &i ;
} ;

struct j {
static const int * const ip ;
} ;
const int * const j::ip = new int ;

/// C: However in k below, even by using constexpr one cannot have an inlined static object and thereby make it compile like j above, because new is by definition runtime, so it cannot be used in a compile time constant expression.

# ifdef TEST2
struct k1 {
constexpr static const int * ip = new int ;
} ;
# endif

/// C: ... and since a constexpr variable *must* be defined at declaration,you cannot initialize it using new outside the class definition either:
/// Q: Um, isn't "constexpr static const int *" just another more sophisticated way of saying "static const int * const", which would mean that what can be done using the latter in j, should be possible using the former in k2below?

# ifdef TEST2
struct k2 {
constexpr static const int * ip ;
} ;
const int * k2::ip = new int ;
# endif

int main () {
delete j::ip ;
# ifdef TEST2
delete k1::ip ;
delete k2::ip ;
# endif
}
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top