Static
members in C++
There
are times when you need a single storage space to be used by all objects of a
class. In C, you would use a global variable, but this is not very safe. Global
data can be modified by anyone, and its name can clash with other identical
names in a large project. It would be ideal if the data could be stored as if
it were global, but be hidden inside a class, and clearly associated with that
class.
This
is accomplished with
static
data members inside a class.
There is a single piece of storage for a
static
data member, regardless of how many objects of that class you create. All
objects share the same
static
storage space for that data member, so it is a way for them to
“communicate” with each other. But the
static
data belongs to the class; its name is scoped inside the class and it can be
public,
private,
or
protected.
Defining
storage for static data members
Because
static
data has a single piece of storage regardless of how many objects are created,
that storage must be defined in a single place. The compiler will not allocate
storage for you, although this was once true, with some compilers. The linker
will report an error if a
static
data member is declared but not defined.
The
definition must occur outside the class (no inlining is allowed), and only one
definition is allowed. Thus it is usual to put it in the implementation file
for the class. The syntax sometimes gives people trouble, but it is actually
quite logical. For example,
class A {
static int i;
public:
//...
};
and
later, in the definition file,
If
you were to define an ordinary global variable, you would say
but
here, the scope resolution operator and the class name are used to specify
A::i. Some
people have trouble with the idea that
A::i
is
private,
and yet here’s something that seems to be manipulating it right out in
the open. Doesn’t this break the protection mechanism? It’s a
completely safe practice for two reasons. First, the only place this
initialization is legal is in the definition. Indeed, if the
static
data were an object with a constructor, you would call the constructor instead
of using the
=
operator. Secondly, once the definition has been made, the end-user cannot make
a second definition – the linker will report an error. And the class
creator is forced to create the definition, or the code won’t link during
testing. This ensures that the definition happens only once and that it’s
in the hands of the class creator.
The
entire initialization expression for a static member is
in the scope of the class. For example,
//: C10:Statinit.cpp
// Scope of static initializer
#include <iostream>
using namespace std;
int x = 100;
class WithStatic {
static int x;
static int y;
public:
void print() const {
cout << "WithStatic::x = " << x << endl;
cout << "WithStatic::y = " << y << endl;
}
};
int WithStatic::x = 1;
int WithStatic::y = x + 1;
// WithStatic::x NOT ::x
int main() {
WithStatic ws;
ws.print();
} ///:~
Here,
the qualification
WithStatic::
extends the scope of
WithStatic
to the entire definition.
static
array initialization
It’s
possible to create
static
const
objects as well as arrays of
static
objects, both
const
and non-
const.
Here’s the syntax you use to initialize such elements:
//: C10:Statarry.cpp {O}
// Initializing static arrays
class Values {
static const int size;
static const float table[4];
static char letters[10];
};
const int Values::size = 100;
const float Values::table[4] = {
1.1, 2.2, 3.3, 4.4
};
char Values::letters[10] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'
};
///:~
As
with all
static
member
data, you must provide a single external definition for the member. These
definitions have internal linkage, so they can be placed in header files. The
syntax for initializing static arrays is the same as any aggregate, but you
cannot use automatic counting. With the exception of the above paragraph, the
compiler must have enough knowledge about the class to create an object by the
end of the class declaration, including the exact sizes of all the components.
Compile-time
constants inside classes
In
Chapter 6 enumerations were introduced as a way to create a compile-time
constant (one that can be evaluated by the compiler in a constant expression,
such as an array size) that’s local to a class. This practice, although
commonly used, is often referred to as the “enum hack”
because it uses enumerations in a way they were not originally intended.
To
accomplish
the same thing using a better approach, you can use a
static
const inside
a class.
[29]
Because it’s both
const
(it
won’t change) and
static
(there’s
only one definition for the whole class), a
static
const
inside
a class can be used as a compile-time constant, like this:
class X {
static const int size;
int array[size];
public:
// ...
};
const int X::size = 100; // Definition
If
you’re using it in a constant expression inside a class, the definition
of the
static
const
member must appear before any instances of the class or member function
definitions (presumably in the header file). As with an ordinary global
const
used with a built-in type, no storage is allocated for the
const,
and it has internal linkage so no clashes occur.
An
additional advantage to this approach is that any built-in type may be made a
member
static
const
.
With
enum,
you’re limited to integral values.
Nested
and local classes
You
can easily put static data members in that are nested inside
other classes. The definition of such members is an intuitive and obvious
extension – you simply use another level of scope resolution. However,
you cannot have static data members inside local classes (classes
defined inside functions).
Thus,
//: C10:Local.cpp {O}
// Static members & local classes
#include <iostream>
using namespace std;
// Nested class CAN have static data members:
class Outer {
class Inner {
static int i; // OK
};
};
int Outer::Inner::i = 47;
// Local class cannot have static data members:
void f() {
class Local {
public:
//! static int i; // Error
// (How would you define i?)
} x;
} ///:~
You
can see the immediate problem with a
static
member in a local class: How do you describe the data member at file scope in
order to define it? In practice, local classes are used very rarely.
static
member functions
You
can also create
static
member functions
that,
like
static
data members, work for the class as a whole rather than for a particular object
of a class. Instead of making a global function that lives in and
“pollutes” the global or local namespace, you bring the function
inside the class. When you create a
static
member function, you are expressing an association with a particular class.
You
can call a
static
member function in the ordinary way, with the dot or the arrow, in association
with an object. However, it’s more typical to call a
static
member function by itself, without any specific object, using the
scope-resolution operator, like this:
class X {
public:
static void f();
};
X::f();
When
you see static member functions in a class, remember that the designer intended
that function to be conceptually associated with the class as a whole.
A
static
member function cannot access ordinary data members, only
static
data members. It can call only other
static
member functions. Normally, the address of the current object (
this)
is quietly passed in when any member function is called, but a
static
member has no
this,
which is the reason it cannot access ordinary members. Thus, you get the tiny
increase in speed afforded by a global function, which doesn’t have the
extra overhead of passing
this,
but the benefits of having the function inside the class.
For
data members,
static
indicates that only one piece of storage for member data exists for all objects
of a class. This parallels the use of
static
to define objects
inside
a function, to mean that only one copy of a local variable is used for all
calls of that function.
Here’s
an example showing
static
data members and
static
member functions used together:
//: C10:StaticMemberFunctions.cpp
class X {
int i;
static int j;
public:
X(int ii = 0) : i(ii) {
// Non-static member function can access
// static member function or data:
j = i;
}
int val() const { return i; }
static int incr() {
//! i++; // Error: static member function
// cannot access non-static member data
return ++j;
}
static int f() {
//! val(); // Error: static member function
// cannot access non-static member function
return incr(); // OK -- calls static
}
};
int X::j = 0;
int main() {
X x;
X* xp = &x;
x.f();
xp->f();
X::f(); // Only works with static members
} ///:~
Because
they have no
this
pointer,
static
member functions can neither access non
static
data members nor call non
static
member functions. (Those functions require a
this
pointer.)
Notice
in
main( )
that a
static
member can be selected using the usual dot or arrow syntax, associating that
function with an object, but also with no object (because a
static
member is associated with a class, not a particular object), using the class
name and scope resolution operator.
Here’s
an interesting feature: Because of the way initialization happens for
static
member objects, you can put a
static
data member of the same class
inside
that class. Here’s an example that allows only a single object of type
egg
to exist by making the constructor private. You can access that object, but you
can’t create any new
egg
objects:
//: C10:Selfmem.cpp
// Static member of same type
// ensures only one object of this type exists.
// Also referred to as a "singleton" pattern.
#include <iostream>
using namespace std;
class Egg {
static Egg e;
int i;
Egg(int ii) : i(ii) {}
public:
static Egg* instance() { return &e; }
int val() { return i; }
};
Egg Egg::e(47);
int main() {
//! Egg x(1); // Error -- can't create an Egg
// You can access the single instance:
cout << Egg::instance()->val() << endl;
} ///:~
The
initialization for
E
happens after the class declaration is complete, so the compiler has all the
information it needs to allocate storage and make the constructor call.
[29]
Your compiler may not have implemented this feature yet; check your local
documentation.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru