I see many time where static data member is used to count creations of objects -
i.e.
1. the static data member is init to 0
2. the static data member is incremented by 1, in the Class' constructor, every time an object is created
However, if you define a global object of a class,
How can you tell that the static data member is initialized BEFORE the constructor of the global object is called? (i.e. before the global object is created).
Because to my understanding, you do not know in advance the order of global objects' creation -
so the Global Object could be created BEFORE the static data member was created and initialized.
> How can you tell that the static data member is initialized BEFORE the constructor of the global object is called?
Let us say, we have:
1 2 3 4 5
struct my_class // in a header
{
A() ; // constructor (dynamic initialization)
staticint cnt ;
};
And int my_class::cnt ; // default initialization in some cpp file
my_class::cnt would be initialized with a value of zero before any constructor of my_class is called.
Variables with static storage duration or thread storage duration shall be zero-initialized before any other initialization takes place.
If, instead, we had: int my_class::cnt = 5 ; // constant initialization
my_class::cnt would be initialized with a value of five before any constructor of my_class is called.
Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place.
If, instead, we had: int my_class::cnt = std::rand() ; // dynamic initialization
There is no guarantee that dynamic initialization of my_class::cnt would have taken place before the constructor of an object (in another tanslation unit at namespace scope) is called.
Regarding Dynamic Initialization,
The following global object rect1 is also dynamically initialized?
even though all its data members initialization values are known at compile time?
I ask as I'm not totally sure what you mean by "dynamic initialization", I read on it in several places but it seems confusing.
> The following global object rect1 is also dynamically initialized?
Yes.
> I'm not totally sure what you mean by "dynamic initialization"
A simple way to look at dynamic initialization is: any initialization where the initializer expression is evaluated at run-time is dynamic initialization.
// namespace scope
int a ; // static initialization (zero initialized)
constint b = 5 * 22 ; // static initialization (constant initialization; 5*22 is evaluated at compile time)
int c = b + 5 ; // static initialization (constant initialization; b+5 is evaluated at compile time)
int d = ++c ; // dynamic initialization (initialization changes the value of another object)
inlineint foo( int arg ) { return arg+5 ; }
int e = foo(5) ; // unspecified (left to the implementation).
// may be static or may be dynamic initialization
// requirement: if statically initialized, the initialization must produce
// the same value as would haven been produced by dynamic initialization.
constexprint bar( int arg ) { return arg+5 ; }
int f = bar(5) ; // static initialization (constant initialization; bar(5) is evaluated at compile time)
int g = bar(++c) ; // dynamic initialization (initialization changes the value of another object)
int h = std::rand() ; // dynamic initialization (std::rand() is evaluated at runtime)
class Rectangle
{
public:
constexpr Rectangle( int a = 8, int b = 4 );
private:
int m_width;
int m_height;
};
constexpr Rectangle::Rectangle( int a, int b ) : m_width(a), m_height(b)
{ /* constexpr constructor must have an empty body */ }
Rectangle r1 ; // static initialization
Rectangle r2( 20, 30 ) ; // static initialization
Rectangle r3( 20, std::rand() ) ; // dynamic initialization
Rectangle r3( 20, ++c ) ; // dynamic initialization
> you can't actually tell which will be initialized first, the static Counter::m_cnt or global rect1, right?
Yes. Example from the standard:
For example,
1 2 3 4 5 6 7 8 9 10
inlinedouble fd() { return 1.0; }
externdouble d1;
double d2 = d1; // unspecified:
// may be statically initialized to 0.0 or
// dynamically initialized to 0.0 if d1 is
// dynamically initialized, or 1.0 otherwise
double d1 = fd(); // may be initialized statically or dynamically to 1.0
One note please, regarding constexpr constructor, vs not-constexpr constructor.
As you said, r1 and r2 will be statically initialized:
1 2 3 4 5 6
constexpr Rectangle::Rectangle( int a, int b ) : m_width(a), m_height(b)
{ /* constexpr constructor must have an empty body */ }
Rectangle r1 ; // static initialization
Rectangle r2( 20, 30 ) ; // static initialization
*BTW, not you must define empty-argument default constructor, for r1 definition? (otherwise compiler produces an error).
If you write the same code, but remove the constexpr keyword, i.e.
1 2 3 4 5 6 7 8
Rectangle::Rectangle( int a, int b )
{
m_width = a;
m_height = b;
}
Rectangle r1 ; // static initialization?
Rectangle r2( 20, 30 ) ; // static initialization?
> BTW, not you must define empty-argument default constructor, for r1 definition?
> (otherwise compiler produces an error).
For Rectangle r1 ; to work, rectangle must be DefaultConstructible.
It must have either a constructor that takes no arguments or a constructor where every argument has a default value.
> If you write the same code, but remove the constexpr keyword,
> will r2 still be statically initialized?
r2 may be statically initialized or it may be dynamically initialized; it is left unspecified.
An implementation is allowed to (but not required to) initialize r2 statically.
(In every implementation that I have checked, with optimizations enabled, r2 would be statically initialized).
From the cppreference page earlier linked to:
The compilers are allowed to initialize dynamically-initialized variables as part of static initialization (essentially, at compile time), if both of these are true:
1) the dynamic version of the initialization does not change the value of any other object of namespace scope prior to its initialization
2) the static version of the initialization produces the same value in the initialized variable as would be produced by the dynamic initialization if all variables not required to be initialized statically were initialized dynamically.
what do you mean by 'an implementation is allowed' in:
r2 may be statically initialized or it may be dynamically initialized; it is left unspecified.
An implementation is allowed to (but not required to) initialize r2 statically.
Also, what do you mean by namespace scope?
I know what namespace is, just not sure what is the meaning of it here.
An implementation is the way that a specific compiler/vendor implements the C++ language, based on the set rules provided by the C++ standard. Basically, that quote is saying that some compilers will initialize r2 statically, but others will not, it depends on the compiler.
Namespace scope is simply that: The scope within a namespace. A scope is normally formed between two curly braces, they are variables that can only be accessed from within that scope. Though of course, in some cases (notably namespace scope) you can still access the variable through the use of the scope resolution operator ('::').
> what do you mean by 'an implementation is allowed' in:
A conforming implementation may have decided to initialize r2 statically.
Another conforming implementation may have decided to initialize r2 dynamically.
Both are conforming implementations.
> what do you mean by namespace scope?
1 2 3 4 5 6 7 8 9 10 11 12 13
// file a.cc
int i = 9 ; // namespace scope
namespace A
{
int j = 10 ; // namespace scope
}
void foo()
{
staticint k = 12 ; // not at namespace scope
}
so by namespace scope you mean file-global scope or namespace-global scope (i.e. objects defined inside namespace, there if weren't the namespace, they were global).
e.g.
1 2 3 4 5 6 7 8 9 10 11 12
//file b.cpp
namespace B
{
class Boarding
{
public:
constchar& getTime()
private:
char *m_time; //not at namespace scop
} boarding; //namespace scop
> by namespace scope you mean file-global scope or namespace-global scope
> (i.e. objects defined inside namespace, there if weren't the namespace, they were global).
Yes. The outermost region in which names can be declared is also a namespace (the global namespace). Names declared here are at namespace scope (called the global namespace scope, which is sometimes referrred to by its abbreviation: global scope)
// file b.cpp
namespace B // 'B' is at namespace scope
{
class Boarding // 'Boarding' is at namespace scope
{
public:
char getTime() ; // 'getTime' is at class scope
private:
char *m_time; // 'm_time' is at class dscope
} boarding; // 'boarding' is at namespace scope
char Boarding::getTime()
{
int x = 7 ; // 'x' is at block scope
struct y // 'y' is at block scope
{
int a = 8 ; // 'a' is at class scope
enumclass colour_t { WHITE, GREY, BLACK } ; // 'colour_t' is at class scope
// and 'GREY' etc. are at enum scope
};
y yy ; // 'yy' is at block scope
return x + yy.a + int( y::colour_t::GREY ) ;
}
namespace C // 'C' is at namespace scope
{
int i = 89 ; // 'i' is at namespace scope
void foo( int arg ) // 'foo' is at namespace scope
// 'arg' is at block scope
{
int j = 23 ; // 'j' is at block scope
label: // 'label' is at function scope
{
staticint k = 45 ; // 'k' is at block scope
}
}
}
}