Friday, October 16, 2009

10.7. static Class Members



10.7. 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'll 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'll 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'll 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.11








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.21–10.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 18–21 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 file—we 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 object—in
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 in line 21 of Fig 10.21











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













Fig. 10.22. Employee class member-function
definitions.


 

 1   // Fig. 10.22: Employee.cpp
2 // Employee class member-function definitions.
3 #include <iostream>
4 using std::cout;
5 using std::endl;
6
7 #include <cstring> // 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; // cannot include keyword static
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 ]; // create space
28 strcpy( firstName, first ); // copy first into object
29
30 lastName = new char[ strlen( last ) + 1 ]; // create space
31 strcpy( lastName, last ); // copy last into object
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'll 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 52–58) and
getLastName (lines 61–67) 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 in lines 22–23, 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.













Fig. 10.23. static data member tracking the
number of objects of a class.


 

 1   // Fig. 10.23: fig10_23.cpp
2 // static data member tracking the number of objects of a class.
3 #include <iostream>
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 << "\n\nEmployee 1: "
26 << e1Ptr->getFirstName() << " " << e1Ptr->getLastName()
27 << "\nEmployee 2: "
28 << e2Ptr->getFirstName() << " " << e2Ptr->getLastName() << "\n\n";
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



Number of employees before instantiation of any objects is 0
Employee constructor for Susan Baker called.
Employee constructor for Robert Jones called.
Number of employees after objects are instantiated is 2

Employee 1: Susan Baker
Employee 2: Robert Jones

~Employee() called for Susan Baker
~Employee() called for Robert Jones
Number of employees after objects are deleted is 0




Software Engineering Observation 10.12








Some
organizations specify in their software engineering standards that all calls to
static member functions be made using the
class name rather than 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 18–19 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 in lines
30 and 32 to deallocate the 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.





 


No comments:

Post a Comment