static Class Members
There is an important exception to the rule that each object of a class has its own copy of all the data members of the class. In certain cases, only one copy of a variable should be shared by all objects of a class. A static data member is used for these and other reasons. Such a variable represents "class-wide" information (i.e., a property of the class shared by all instances, not a property of a specific object of the class). The declaration of a static member begins with keyword static. Recall that the versions of class GradeBook in Chapter 7 use static data members to store constants representing the number of grades that all GradeBook objects can hold.
Let us further motivate the need for static class-wide data with an example. Suppose that we have a video game with Martians and other space creatures. Each Martian tends to be brave and willing to attack other space creatures when the Martian is aware that there are at least five Martians present. If fewer than five are present, each Martian becomes cowardly. So each Martian needs to know the martianCount. We could endow each instance of class Martian with martianCount as a data member. If we do, every Martian will have a separate copy of the data member. Every time we create a new Martian, we will have to update the data member martianCount in all Martian objects. Doing this would require every Martian object to have, or have access to, handles to all other Martian objects in memory. This wastes space with the redundant copies and wastes time in updating the separate copies. Instead, we declare martianCount to be static. This makes martianCount class-wide data. Every Martian can access martianCount as if it were a data member of the Martian, but only one copy of the static variable martianCount is maintained by C++. This saves space. We save time by having the Martian constructor increment static variable martianCount and having the Martian destructor decrement martianCount. Because there is only one copy, we do not have to increment or decrement separate copies of martianCount for each Martian object.
Performance Tip 10.3
Use static data members to save storage when a single copy of the data for all objects of a class will suffice. |
Although they may seem like global variables, a class's static data members have class scope. Also, static members can be declared public, private or protected. A fundamental-type static data member is initialized by default to 0. If you want a different initial value, a static data member can be initialized once (and only once). A const static data member of int or enum type can be initialized in its declaration in the class definition. However, all other static data members must be defined at file scope (i.e., outside the body of the class definition) and can be initialized only in those definitions. Note that static data members of class types (i.e., static member objects) that have default constructors need not be initialized because their default constructors will be called.
A class's private and protected static members are normally accessed through public member functions of the class or through friends of the class. (In Chapter 12, we will see that a class's private and protected static members can also be accessed through protected member functions of the class.) A class's static members exist even when no objects of that class exist. To access a public static class member when no objects of the class exist, simply prefix the class name and the binary scope resolution operator (::) to the name of the data member. For example, if our preceding variable martianCount is public, it can be accessed with the expression Martian::martianCount when there are no Martian objects. (Of course, using public data is discouraged.)
A class's public static class members can also be accessed through any object of that class using the object's name, the dot operator and the name of the member (e.g., myMartian.martianCount). To access a private or protected static class member when no objects of the class exist, provide a public static member function and call the function by prefixing its name with the class name and binary scope resolution operator. (As we will see in Chapter 12, a protected static member function can serve this purpose, too.) A static member function is a service of the class, not of a specific object of the class.
Software Engineering Observation 10.10
A class's static data members and static member functions exist and can be used even if no objects of that class have been instantiated. |
The program of Figs. 10.2110.23 demonstrates a private static data member called count (Fig. 10.21, line 21) and a public static member function called getCount (Fig. 10.21, line 15). In Fig. 10.22, line 14 defines and initializes the data member count to zero at file scope and lines 1821 define static member function getCount. Notice that neither line 14 nor line 18 includes keyword static, yet both lines refer to static class members. When static is applied to an item at file scope, that item becomes known only in that file. The static members of the class need to be available from any client code that accesses the file, so we cannot declare them static in the .cpp filewe declare them static only in the .h file. Data member count maintains a count of the number of objects of class Employee that have been instantiated. When objects of class Employee exist, member count can be referenced through any member function of an Employee objectin Fig. 10.22, count is referenced by both line 33 in the constructor and line 48 in the destructor. Also, note that since count is an int, it could have been initialized in the header file at line 21 of Fig. 10.21.
Figure 10.21. Employee class definition with a static data member to track the number of Employee objects in memory.
1 // Fig. 10.21: Employee.h 2 // Employee class definition. 3 #ifndef EMPLOYEE_H 4 #define EMPLOYEE_H 5 6 class Employee 7 { 8 public: 9 Employee( const char * const, const char * const ); // constructor 10 ~Employee(); // destructor 11 const char *getFirstName() const; // return first name 12 const char *getLastName() const; // return last name 13 14 // static member function 15 static int getCount(); // return number of objects instantiated 16 private: 17 char *firstName; 18 char *lastName; 19 20 // static data 21 static int count; // number of objects instantiated 22 }; // end class Employee 23 24 #endif |
Figure 10.22. Employee class member-function definitions.
(This item is displayed on pages 555 - 556 in the print version)
1 // Fig. 10.22: Employee.cpp 2 // Member-function definitions for class Employee. 3 #include 4 using std::cout; 5 using std::endl; 6 7 #include // strlen and strcpy prototypes 8 using std::strlen; 9 using std::strcpy; 10 11 #include "Employee.h" // Employee class definition 12 13 // define and initialize static data member at file scope 14 int Employee::count = 0; 15 16 // define static member function that returns number of 17 // Employee objects instantiated (declared static in Employee.h) 18 int Employee::getCount() 19 { 20 return count; 21 } // end static function getCount 22 23 // constructor dynamically allocates space for first and last name and 24 // uses strcpy to copy first and last names into the object 25 Employee::Employee( const char * const first, const char * const last ) 26 { 27 firstName = new char[ strlen( first ) + 1 ]; 28 strcpy( firstName, first ); 29 30 lastName = new char[ strlen( last ) + 1 ]; 31 strcpy( lastName, last ); 32 33 count++; // increment static count of employees 34 35 cout << "Employee constructor for " << firstName 36 << ' ' << lastName << " called." << endl; 37 } // end Employee constructor 38 39 // destructor deallocates dynamically allocated memory 40 Employee::~Employee() 41 { 42 cout << "~Employee() called for " << firstName 43 << ' ' << lastName << endl; 44 45 delete [] firstName; // release memory 46 delete [] lastName; // release memory 47 48 count--; // decrement static count of employees 49 } // end ~Employee destructor 50 51 // return first name of employee 52 const char *Employee::getFirstName() const 53 { 54 // const before return type prevents client from modifying 55 // private data; client should copy returned string before 56 // destructor deletes storage to prevent undefined pointer 57 return firstName; 58 } // end function getFirstName 59 60 // return last name of employee 61 const char *Employee::getLastName() const 62 { 63 // const before return type prevents client from modifying 64 // private data; client should copy returned string before 65 // destructor deletes storage to prevent undefined pointer 66 return lastName; 67 } // end function getLastName |
Common Programming Error 10.10
It is a compilation error to include keyword static in the definition of a static data members at file scope. |
In Fig. 10.22, note the use of the new operator (lines 27 and 30) in the Employee constructor to dynamically allocate the correct amount of memory for members firstName and lastName. If the new operator is unable to fulfill the request for memory for one or both of these character arrays, the program will terminate immediately. In Chapter 16, we will provide a better mechanism for dealing with cases in which new is unable to allocate memory.
Also note in Fig. 10.22 that the implementations of functions getFirstName (lines 5258) and getLastName (lines 6167) return pointers to const character data. In this implementation, if the client wishes to retain a copy of the first name or last name, the client is responsible for copying the dynamically allocated memory in the Employee object after obtaining the pointer to const character data from the object. It is also possible to implement getFirstName and getLastName, so the client is required to pass a character array and the size of the array to each function. Then the functions could copy the first or last name into the character array provided by the client. Once again, note that we could have used class string here to return a copy of a string object to the caller rather than returning a pointer to the private data.
Figure 10.23 uses static member function getCount to determine the number of Employee objects currently instantiated. Note that when no objects are instantiated in the program, the Employee::getCount() function call is issued (lines 14 and 38). However, when objects are instantiated, function getCount can be called through either of the objects, as shown in the statement at lines 2223, which uses pointer e1Ptr to invoke function getCount. Note that using e2Ptr->getCount() or Employee::getCount() in line 23 would produce the same result, because getCount always accesses the same static member count.
Figure 10.23. static data member tracking the number of objects of a class.
(This item is displayed on pages 556 - 557 in the print version)
1 // Fig. 10.23: fig10_23.cpp 2 // Driver to test class Employee. 3 #include 4 using std::cout; 5 using std::endl; 6 7 #include "Employee.h" // Employee class definition 8 9 int main() 10 { 11 // use class name and binary scope resolution operator to 12 // access static number function getCount 13 cout << "Number of employees before instantiation of any objects is " 14 << Employee::getCount() << endl; // use class name 15 16 // use new to dynamically create two new Employees 17 // operator new also calls the object's constructor 18 Employee *e1Ptr = new Employee( "Susan", "Baker" ); 19 Employee *e2Ptr = new Employee( "Robert", "Jones" ); 20 21 // call getCount on first Employee object 22 cout << "Number of employees after objects are instantiated is " 23 << e1Ptr->getCount(); 24 25 cout << " Employee 1: " 26 << e1Ptr->getFirstName() << " " << e1Ptr->getLastName() 27 << " Employee 2: " 28 << e2Ptr->getFirstName() << " " << e2Ptr->getLastName() << " "; 29 30 delete e1Ptr; // deallocate memory 31 e1Ptr = 0; // disconnect pointer from free-store space 32 delete e2Ptr; // deallocate memory 33 e2Ptr = 0; // disconnect pointer from free-store space 34 35 // no objects exist, so call static member function getCount again 36 // using the class name and the binary scope resolution operator 37 cout << "Number of employees after objects are deleted is " 38 << Employee::getCount() << endl; 39 return 0; 40 } // end main
|
Software Engineering Observation 10.11
Some organizations specify in their software engineering standards that all calls to static member functions be made using the class name and not an object handle. |
A member function should be declared static if it does not access non-static data members or non-static member functions of the class. Unlike non-static member functions, a static member function does not have a this pointer, because static data members and static member functions exist independently of any objects of a class. The this pointer must refer to a specific object of the class, and when a static member function is called, there might not be any objects of its class in memory.
Common Programming Error 10.11
Using the this pointer in a static member function is a compilation error. |
Common Programming Error 10.12
Declaring a static member function const is a compilation error. The const qualifier indicates that a function cannot modify the contents of the object in which it operates, but static member functions exist and operate independently of any objects of the class. |
Lines 1819 of Fig. 10.23 use operator new to dynamically allocate two Employee objects. Remember that the program will terminate immediately if it is unable to allocate one or both of these objects. When each Employee object is allocated, its constructor is called. When delete is used at lines 30 and 32 to deallocate the two Employee objects, each object's destructor is called.
Error-Prevention Tip 10.2
After deleting dynamically allocated memory, set the pointer that referred to that memory to 0. This disconnects the pointer from the previously allocated space on the free store. This space in memory could still contain information, despite having been deleted. By setting the pointer to 0, the program loses any access to that free-store space, which, in fact, could have already been reallocated for a different purpose. If you didn't set the pointer to 0, your code could inadvertently access this new information, causing extremely subtle, nonrepeatable logic errors. |