Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.Please state the document version you're referring to, as found in the title (in this document: 8.3.1) and please state chapter and paragraph name or number you're referring to.
All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.
In the previous chapters we provided examples of classes where each object had its own set of data members data. Each of the class's member functions could access any member of any object of its class.
In some situations it may be desirable to define common data fields, that may be accessed by all objects of the class. For example, the name of the startup directory, used by a program that recursively scans the directory tree of a disk. A second example is a variable that indicates whether some specific initialization has occurred. In that case the object that was constructed first would perform the initialization and would set the flag to `done'.
Such situations are als encountered in C, where several functions need to
access the same variable. A common solution in C is to define all these
functions in one source file and to define the variable
static
: the
variable name will not be visible beyond the scope of the source file. This
approach is quite valid, but violates our philosophy of using only one
function per source file. Another C-solution is to give the variable in
question an unusual name, e.g., _6uldv8
, hoping that other program parts
won't use this name by accident. Neither the first, nor the second legacy
C solution is elegant.
C++ solves the problem by defining
static members
: data and functions,
common to all objects of a class and (when defined in the private section)
inaccessible outside of the class. These static members are this chapter's
topic.
Static members cannot be defined as
virtual
functions. A virtual member function is an ordinary member in that it has a
this
pointer. As static member functions have no this
pointer, they
cannot be declared virtual.
static
; be it in the public
or private
section of the class interface. Such a data member is created
and initialized only once, in contrast to non-static data members which are
created again and again for each object of the class.
Static data members are created as soon as the program starts. Even though they're created at the very beginning of a program's execution cycle they are nevertheless true members of their classes.
It is suggested to prefix the names of static member with s_
so they may
easily be distinguished (in class member functions) from the class's data
members (which should preferably start with d_
).
Public static data members are global variables. They may be accessed by all of the program's code, simply by using their class names, the scope resolution operator and their member names. Example:
class Test { static int s_private_int; public: static int s_public_int; }; int main() { Test::s_public_int = 145; // OK Test::s_private_int = 12; // wrong, don't touch // the private parts }No executable program will result from the example. It merely illustrates the interface, and not the implementation of
static
data members,
which is discussed next.
class Directory { static char s_path[]; public: // constructors, destructors, etc. };The data member
s_path[]
is a private static data member. During
the program's execution only one Directory::s_path[]
exists,
even though multiple objects of the class Directory
may exist. This
data member could be inspected or altered by the constructor, destructor or by
any other member function of the class Directory
.
Since constructors are called for each new object of a class, static data members are not initialized by constructors. At most they are modified. The reason for this is that static data members exist before any constructor of the class has been called. Static data members are initialized when they are defined, outside of any member function, exactly like the initialization of ordinary (non-class) global variables.
The definition and initialization of a static data member usually occurs
in one of the source files of the class functions, preferably in a source file
dedicated to the definition of static data members, called
data.cc
.
The data member s_path[]
, used above, could thus be
defined and initialized as follows in a file data.cc
:
include "directory.ih" char Directory::s_path[200] = "/usr/local";In the class interface the static member is actually only declared. In its implementation (definition) its type and class name are explicitly mentioned. Note also that the size specification can be left out of the interface, as shown above. However, its size is (either explicitly or implicitly) required when it is defined.
Note that any source file could contain the definition of the static
data members of a class. A separate data.cc
source file is advised, but
the source file containing, e.g., main()
could be used as well. Of course,
any source file defining static data of a class must also include the header
file of that class, in order for the static data member to be known to the
compiler.
A second example of a useful private static data member is given below. Assume
that a class Graphics
defines the communication of a program with a
graphics-capable device (e.g., a VGA screen). The initialization of the
device, which in this case would be to switch from text mode to graphics mode,
is an action of the constructor and depends on a static
flag variable
s_nobjects
. The variable s_nobjects
simply counts the number of
Graphics
objects which are present at one time. Similarly, the destructor
of the class may switch back from graphics mode to text mode when the last
Graphics
object ceases to exist. The class interface for this
Graphics
class might be:
class Graphics { static int s_nobjects; // counts # of objects public: Graphics(); ~Graphics(); // other members not shown. private: void setgraphicsmode(); // switch to graphics mode void settextmode(); // switch to text-mode }The purpose of the variable
s_nobjects
is to count the number of
objects existing at a particular moment in time. When the first object is
created, the graphics device is initialized. At the destruction of the last
Graphics
object, the switch from graphics mode to text mode is made:
int Graphics::s_nobjects = 0; // the static data member Graphics::Graphics() { if (!s_nobjects++) setgraphicsmode(); } Graphics::~Graphics() { if (!--s_nobjects) settextmode(); }Obviously, when the class
Graphics
would define more than one
constructor, each constructor would need to increase the variable
s_nobjects
and would possibly have to initialize the graphics mode.
s_path[]
(cf. section 11.1) could be
declared in the public section of the class definition. This would allow all
the program's code to access this variable directly:
int main() { getcwd(Directory::s_path, 199); }A declaration is not a definition. Consequently the variable
s_path
still has to be defined. This implies that some source file still needs to
contain s_path[]
array's definition.
const
data members may be initialized in the
class interface if these data members are of integral or built-in primitive
data types. So, in the following example the first three static data members
can be initialized since int
and double
are primitive built-in types
and int
and enum
types are integral types. The static data member
s_str
cannot be initialized in the class interface since string
is
neither a primitive built-in nor an integral data type:
class X { public: enum Enum { FIRST, }; static int const s_x = 34; static Enum const s_type = FIRST; static double const s_d = 1.2; static string const s_str = "a"; // won't compile };The compiler may decide to initialize static
const
data members as
mere constant values, in which they don't have addresses. If the compiler does
so, such static const
data members behave as though they were values of
an enum
defined by the class. Consequently they are not variables and so
it is not possible to determine their addresses. Note that trying to obtain
the address of such a constant value does not create a compilation problem,
but it does create a linking problem as the static const
variable that
is initialized as a mere constant value does not exist in addressable memory.
A statement like int *ip = &X::s_x
may therefore compile
correctly, but may then fail to link. Static variables that are
explicitly defined in a source file can be linked correctly, though. So,
in the following example the address of X::s_x
cannot be solved by the
linker, but the address of X::s_y
can be determined:
class X { public: static int const s_x = 34; static int const s_y; }; int const X::s_y = 12; int main() { int const *ip = &X::s_x; // compiles, but fails to link ip = &X::s_y; // compiles and links correctly }
Static member functions
can access all static
members of their class, but also the members (private
or public
)
of objects of their class if they are informed about the existence of
these objects (as in the upcoming example). As static member functions are not
associated with any object of their class they do not have a
this
pointer. In fact, a static member function is completely comparable
to a
global function, not associated with any class (i.e., in practice they
are. See the next section (11.2.1) for a subtle note). Since
static member functions do not require an associated object, static member
functions declared in the public section of a class interface may be called
without specifying an object of its class. The following example illustrates
this characteristic of static member functions:
class Directory { string d_currentPath; static char s_path[]; public: static void setpath(char const *newpath); static void preset(Directory &dir, char const *newpath); }; inline void Directory::preset(Directory &dir, char const *newpath) { // see the text below dir.d_currentPath = newpath; // 1 } char Directory::s_path[200] = "/usr/local"; // 2 void Directory::setpath(char const *newpath) { if (strlen(newpath) >= 200) throw "newpath too long"; strcpy(s_path, newpath); // 3 } int main() { Directory dir; Directory::setpath("/etc"); // 4 dir.setpath("/etc"); // 5 Directory::preset(dir, "/usr/local/bin"); // 6 dir.preset(dir, "/usr/local/bin"); // 7 }
Note that static member functions can be defined as inline functions.
string
or a pointer to dynamic memory could
be used.
s_path[]
. Note that only static members are used.
setpath()
is called. It is a static member, so no
object is required. But the compiler must know to which class the function
belongs, so the class is mentioned using the scope resolution operator.
dir
is used to tell
the compiler that we're talking about a function in the Directory
class. Static member functions can be called as normal member
functions, but this does not imply that the static member function receives
the object's address as a this
pointer. Here the member-call syntax is
used as an alternative for the classname plus scope resolution operator
syntax.
currentPath
is altered. As in 4, the class and the scope
resolution operator are used.
dir
is used to
tell the compiler that we're talking about a function in the Directory
class. Here in particular note that this is not using preset()
as an
ordinary member function of dir
: the function still has no
this
-pointer, so dir
must be passed as argument to inform the static
member function preset
about the object whose currentPath
member it
should modify.
In the example only public static member functions were used. C++ also allows the definition of private static member functions. Such functions can only be called by member functions of their class.
In practice the calling conventions are identical, implying that the address of a static member function could be used as an argument of functions having parameters that are pointers to (global) functions.
If unpleasant surprises must be avoided at all cost, it is suggested to create global classless wrapper functions around static member functions that must be used as call back functions for other functions.
Recognizing that the traditional situations in which call back functions
are used in C are tackled in C++ using template algorithms
(cf. chapter 19), let's assume that we have a class Person
having data members representing the person's name, address, phone and
mass. Furthermore, assume we want to sort an array of pointers to Person
objects, by comparing the Person
objects these pointers point to. Keeping
things simple, we assume that the following public static member exists:
int Person::compare(Person const *const *p1, Person const *const *p2);A useful characteristic of this member is that it may directly inspect the required data members of the two
Person
objects passed to the member
function using pointers to pointers (
double pointers).
Most compilers will allow us to pass this function's address as the
address of the comparison function for the standard C
qsort()
function. E.g.,
qsort ( personArray, nPersons, sizeof(Person *), reinterpret_cast<int(*)(const void *, const void *)>(Person::compare) );However, if the compiler uses different calling conventions for static members and for classless functions, this might not work. In such a case, a classless wrapper function like the following may be used profitably:
int compareWrapper(void const *p1, void const *p2) { return Person::compare ( reinterpret_cast<Person const *const *>(p1), reinterpret_cast<Person const *const *>(p2) ); }resulting in the following call of the
qsort()
function:
qsort(personArray, nPersons, sizeof(Person *), compareWrapper);Note:
Person
objects rather than double pointers);
qsort()
,
requiring the specification of call back functions are seldom used. Instead
using existing generic template algorithms is preferred (cf. chapter
19).