Chapter 10: Static data and functions

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: 6.5.0) 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 have shown examples of classes where each object of a class had its own set of public or private data. Each public or private member could access any member of any object of its class.

In some situations it may be desirable that one or more common data fields exist, which are accessible to 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 flag variable, which states whether some specific initialization has occurred: only the first object of the class would perform the necessary initialization and would set the flag to `done'.

Such situations are analogous to C code, 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 declare the variable as a static: the variable name is then not known 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 C-like solution is elegant.

C++'s solution is to define static members: data and functions, common to all objects of a class and inaccessible outside of the class. These static members are the topic of this chapter.

10.1: Static data

Any data member of a class can be declared static; be it in the public or private section of the class definition. 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 separate object of the class.

Static data members are created when the program starts. Note, however, that they are always created as true members of their classes. It is suggested to prefix static member names with s_ in order to distinguish them (in class member functions) from the class's data members (which should preferably start with d_).

Public static data members are like `normal' global variables: they can be accessed by all code of the program, simply using their class names, the scope resolution operator and their member names. This is illustrated in the following 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
        return 0;
    }
This code fragment is not suitable for consumption by a C++ compiler: it merely illustrates the interface, and not the implementation of static data members, which is discussed next.

10.1.1: Private static data

To illustrate the use of a static data member which is a private variable in a class, consider the following example:
    class Directory
    {
        static char s_path[];

        public:
            // constructors, destructors, etc. (not shown)
    };
The data member s_path[] is a private static data member. During the execution of the program, only one Directory::s_path[] exists, even though more than one object 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 never 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 all member functions, in the same way as other global variables are initialized.

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 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.

10.1.2: Public static data

Data members can be declared in the public section of a class, although this is not common practice (as this would violate the principle of data hiding). E.g., when the static data member s_path[] from section 10.1 would be declared in the public section of the class definition, all program code could access this variable:
    int main()
    {
        getcwd(Directory::s_path, 199);
    }
Note that the variable s_path would still have to be defined. As before, the class interface would only declare the array s_path[]. This means that some source file would still need to contain the definition of the s_path[] array.

10.1.3: Initializing static const data

Static const data members may be initialized in the class interface if these data members are of an integral data type. So, in the following example the first three static data members can be initialized since int enum and double types are integral data members. The last static data member cannot be initialized in the class interface since string is not 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
    };
Static const integral data members initialized in the class interface are not addressable variables. They are mere symbolic names for their associated values. Since they are not variables, it is not possible to determine their addresses. Note that this is not a compilation problem, but a linking problem. The static const variable that is initialized in the class interface does not exist as an addressable entity.

A statement like int *ip = &X::s_x will therefore compile correctly, but will 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 solved by the linker:

    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
    }

10.2: Static member functions

Besides static data members, C++ allows the definition of static member functions. Similar to the concept of static data, in which these variables are shared by all objects of the class, static member functions exist without any associated object of their class.

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. Static member functions are themselves not associated with any object of their class. Consequently, 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 (10.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 *path);
    };
    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
    }

In the example only public static member functions were used. C++ also allows the definition of private static member functions: these functions can only be called by member functions of their class.

10.2.1: Calling conventions

As noted in the previous section, static (public) member functions are comparable to classless functions. However, formally this statement is not true, as the C++ standard does not prescribe the same calling conventions for static member functions and for classless global functions.

In practice these calling conventions are identical, implying that the address of a static member function could be used as an argument in 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 17), let's assume that we have a class Person having data members representing the person's name, address, phone and weight. Furthermore, assume we want to sort an array of pointers to Person objects, by comparing the Person objects these pointers point to. To keep things simple, we assume that a public static

    int Person::compare(Person const *const *p1, Person const *const *p2);
exists. 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 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: