In C++ it is generally the case that you can access the variables that are declared inside whatever level of curly braces you are currently in. Curly braces and scope at least roughly correspond.
But a data definition includes an initializer, which is code. The code that initializes the static data member is not allowed to access any of the non-static data members of the class. (Because those are members of objects, and there is a one-to-many relationship between the static member and the objects and in any case the static member is initialized before any of the objects of the class are created.)
So example:
static int a=0;
class foo {
int b;
static int c = b+1; // what does this mean????
};
int foo::c = a+2; // This makes sense. We are initializing using only other statics.
Let's look at another similar situation (from C) where the language designers made a different decision that has historically caused a lot of trouble. In C static
usually means "has the lifetime from the time the compilation unit is loaded until the compilation unit is unloaded." Unfortunately there is one case where it means something different:
static int y = 0;
int foo(int x) {
static int bar = x+y; // bar gets initialized when??? And what value does it get?
bar += x;
return bar;
}
When does bar
get initialized? At the time of the first call to foo
! So its initial value depends on x
(which kind of makes sense) and whatever the value of y
is at the moment (which is confusing at best.) This also has performance and correctness implications because of multithreading: foo
's initializer needs to be protected with a guard that guarantees the initializer gets executed only once, and the guard needs to be atomic with respect to multithreading! So the above code is equivalent to the following:
static int y = 0;
static int foo_initializer_has_been_called=false;
int foo(int x) {
static int bar;
if (foo_initializer_has_been_called) { // actually this needs to be an atomic
bar = x+y; // test-and-test-and-set to handle
foo_initializer_has_been_called = true; // multithreading
}
bar += x;
return bar;
}
Arguably the designers of C had no choice because there was no other place to write the initializer for a variable with static lifetime but local scope. The designers of C++ couldn't fix the mistake that the C designers made, but they could avoid making it a second time, because they have the class_name::member_name
syntax to use outside the class declaration. So a static declared inside a class has exactly the same lifetime as a static declared outside the class, and the initializer is written at the global scope to make it clear that the lifetime and scope of accessible variables during initialization is the same as it would be if the static was declared outside the class.