Inapoi
Inainte
Cuprins
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.
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
common to put it in the implementation file for the class. The syntax sometimes
gives people trouble, but it is actually quite logical. For example, if you
create a static data member inside a class like this:
class A {
static int i;
public:
//...
};
Then you must define storage for that
static data member in the definition file like this:
int A::i = 1;
If you were to define an ordinary global
variable, you would say
int i = 1;
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. Second, 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
Chapter 8 introduced the static
const variable that allows you to define a constant value inside a class
body.
It’s
also possible to create arrays of static objects, both const and
non-const. The syntax is reasonably consistent:
//: C10:StaticArray.cpp
// Initializing static arrays in classes
class Values {
// static consts are initialized in-place:
static const int scSize = 100;
static const long scLong = 100;
// Automatic counting works with static arrays.
// Arrays, Non-integral and non-const statics
// must be initialized externally:
static const int scInts[];
static const long scLongs[];
static const float scTable[];
static const char scLetters[];
static int size;
static const float scFloat;
static float table[];
static char letters[];
};
int Values::size = 100;
const float Values::scFloat = 1.1;
const int Values::scInts[] = {
99, 47, 33, 11, 7
};
const long Values::scLongs[] = {
99, 47, 33, 11, 7
};
const float Values::scTable[] = {
1.1, 2.2, 3.3, 4.4
};
const char Values::scLetters[] = {
'a', 'b', 'c', 'd', 'e',
'f', 'g', 'h', 'i', 'j'
};
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'
};
int main() { Values v; } ///:~
With static consts of integral
types you can provide the definitions inside the class, but for everything else
(including arrays of integral types, even if they are static const)
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 for any aggregate,
including automatic counting.
You can also create static const
objects of class types and arrays of such objects. However, you cannot
initialize them using the “inline syntax” allowed for static
consts of integral built-in types:
//: C10:StaticObjectArrays.cpp
// Static arrays of class objects
class X {
int i;
public:
X(int ii) : i(ii) {}
};
class Stat {
// This doesn't work, although
// you might want it to:
//! static const X x(100);
// Both const and non-const static class
// objects must be initialized externally:
static X x2;
static X xTable2[];
static const X x3;
static const X xTable3[];
};
X Stat::x2(100);
X Stat::xTable2[] = {
X(1), X(2), X(3), X(4)
};
const X Stat::x3(100);
const X Stat::xTable3[] = {
X(1), X(2), X(3), X(4)
};
int main() { Stat v; } ///:~
The initialization of both const
and non-const static arrays of class objects must be performed the
same way, following the typical static definition
syntax.
Nested and local classes
You can easily put static data members in
classes 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
(a local class is a class
defined inside a function). Thus,
//: C10:Local.cpp
// 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;
}
int main() { Outer x; f(); } ///:~
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:
//: C10:SimpleStaticMemberFunction.cpp
class X {
public:
static void f(){};
};
int main() {
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 because a static member function doesn’t have the extra
overhead of passing this. At the same time you get 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.
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:Singleton.cpp
// Static member of same type, ensures that
// only one object of this type exists.
// Also referred to as the "singleton" pattern.
#include <iostream>
using namespace std;
class Egg {
static Egg e;
int i;
Egg(int ii) : i(ii) {}
Egg(const Egg&); // Prevent copy-construction
public:
static Egg* instance() { return &e; }
int val() const { 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.
To completely prevent the creation of any
other objects, something else has been added: a second private constructor
called the
copy-constructor. At this
point in the book, you cannot know why this is necessary since the copy
constructor will not be introduced until the next chapter. However, as a sneak
preview, if you were to remove the copy-constructor defined in the example
above, you’d be able to create an Egg object like
this:
Egg e = *Egg::instance();
Egg e2(*Egg::instance());
Both of these use the copy-constructor,
so to seal off that possibility the copy-constructor is declared as private (no
definition is necessary because it never gets called). A large portion of the
next chapter is a discussion of the copy-constructor so it should become clear
to you
then.
 |
|