Chapter 10: Static data and functions

We're always interested in getting feedback. E-mail us if you like this guide, if you think that important material is omitted, if you encounter errors in the code examples or in the documentation, if you find any typos, or generally just if you feel like e-mailing. Send your email to Frank Brokken.

Please state the document version you're referring to, as found in the title (in this document: 5.2.0a) and please state the paragraph you're referring to.

All mail received is seriously considered, and new (sub)releases of the Annotations will normally reflect your suggestions for improvements. Except for the incidental case I will not otherwise acknowledge the receipt of suggestions for improvements. Please don't misinterpret this for lack of appreciation.

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 function could access the members of objects of that 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. An example of such a situation is the name of the startup directory in a program which 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 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 doesn't stroke with our philosophy of one function per source file. Another C-solution is to give the variable in question an unusual name, e.g., _6uldv8, and then to hope 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 functions and data will be discussed in this chapter.

10.1: Static data

A data member of a class can be declared static; be it in the public or private part 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 executing. Note, however, that they are always created as 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' data members.

Static data members which are declared public are like `normal' global variables: they can be reached by all code of the program by simply using their names, together with their class names and the scope resolution operator. This is illustrated in the following example:

    class Test
    {
        public:
            static int
                s_public_int;
        private:
            static int
                s_private_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 only 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 path[] is a private static data member. During the execution of the program, only one Directory::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 the static data members exist before any constructor of the class has been called. The static data members can be initialized during their definition, outside of all member functions, in the same way as 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 path[] from the above class Directory 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. At its implementation (definition) its type and class name are explicitly stated. Note also that the size specification can be left out of the interface, as is shown in the above array path[]. However, its size is (either explicitly or implicitly) needed at its definition.

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 definining 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 nobjects. The variable 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 nobjects is to count the number of objects which exist at one given 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 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 (such a setup would violate the principle of data hiding). E.g., when the static data member 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.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 have not this pointer. In fact, a static member function is completely comparable to a global function, not associated with any class. This implies that the address of a static member function could be used in functions having parameters that are pointers to (global) functions. Since static member functions are comparable to ordinary global functions, static member functions that are declared in the public section of a class interface can be called without specifying an object of the class. The following example illustrates these characteristics 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);
    };
                                                    // see the text below
    char Directory::s_path[200] = "/usr/local";     // 1

    void Directory::setpath(char const *newpath)    
    {
        if (strlen(newpath) >= 200)                 
            throw "newpath too long";

        strcpy(s_path, newpath);                    // 2
    }

    void Directory::preset(Directory &dir, char const *newpath)
    {
        dir.d_currentPath = 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 private static member functions: these functions can only be called by member functions of their class.

Finally it is noted that static member functions cannot be defined as inline functions. Since a static function can be used as an ordinary function at the global level, it must have an address. The code of inline functions is normally directly inserted into the code of the program: not a true function, but an alias for a few statements. With inline functions we have no ` function address', Consequently, inline functions lack a basic requirement for static member functions: they have no address.