Day 14
C++ offers
a number of ways to limit the scope and impact of variables and pointers. So
far you've seen how to create global variables, local function variables,
pointers to variables, and class member variables. Today you learn
What static
member variables and static member functions are.
How to use
static member variables and static member functions.
How to
create and manipulate pointers to functions and pointers to member functions.
How to work
with arrays of pointers to functions.
Until now,
you have probably thought of the data in each object as unique to that object
and not shared among objects in a class. For example, if you have five Cat
objects, each has its own age, weight, and other data. The age of one does not
affect the age of another.
There are
times, however, when you'll want to keep track of a pool of data. For example,
you might want to know how many objects for a specific class have been created
in your program, and how many are still in existence. Static member variables
are shared among all instances of a class. They are a compromise between global
data, which is available to all parts of your program, and member data, which
is usually available only to each object.
You can
think of a static member as belonging to the class rather than to the object.
Normal member data is one per object, but static members are one per class.
Listing 14.1 declares a Cat object with a static data member, HowManyCats. This
variable keeps track of how many Cat objects have been created. This is done by
incrementing the static variable, HowManyCats, with each construction and
decrementing it with each destruction.
1: //Listing 14.1 static data members
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++;
}
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge =
age; }
12: static int HowManyCats;
13:
14: private:
15: int itsAge;
16:
17: };
18:
19: int Cat::HowManyCats = 0;
20:
21: int main()
22: {
23: const int MaxCats = 5; int i;
24: Cat *CatHouse[MaxCats];
25: for (i = 0; i<MaxCats; i++)
26: CatHouse[i] = new Cat(i);
27:
28: for (i = 0; i<MaxCats; i++)
29: {
30: cout << "There are ";
31: cout << Cat::HowManyCats;
32: cout << " cats
left!\n";
33: cout << "Deleting the one
which is ";
34: cout <<
CatHouse[i]->GetAge();
35: cout << " years
old\n";
36: delete CatHouse[i];
37: CatHouse[i] = 0;
38: }
39: return 0;
40: }
Output:
There are 5 cats left!
Deleting
the one which is 0 years old
There are 4
cats left!
Deleting
the one which is 1 years old
There are 3
cats left!
Deleting
the one which is 2 years old
There are 2
cats left!
Deleting
the one which is 3 years old
There are 1
cats left!
Deleting
the one which is 4 years old
Analysis:
On lines 5 to 17 the simplified class Cat is declared. On line 12, HowManyCats
is declared to be a static member variable of type int.
The
declaration of HowManyCats does not define an integer; no storage space is set
aside. Unlike the non-static member variables, no storage space is set aside by
instantiating a Cat object, because the HowManyCats member variable is not in
the object. Thus, on line 19 the variable is defined and initialized.
It is a
common mistake to forget to define the static member variables of classes.
Don't let this happen to you! Of course, if it does, the linker will catch it
with a pithy error message such as the following:
undefined
symbol Cat::HowManyCats
You don't
need to do this for itsAge, because it is a non-static member variable and is
defined each time you make a Cat object, which you do here on line 26.
The
constructor for Cat increments the static member variable on line 8. The
destructor decrements it on line 9. Thus, at any moment, HowManyCats has an
accurate measure of how many Cat objects were created but not yet destroyed.
The driver
program on lines 21-40 instantiates five Cats and puts them in an array. This
calls five Cat constructors, and thus HowManyCats is incremented five times
from its initial value of 0.
The program
then loops through each of the five positions in the array and prints out the
value of HowManyCats before deleting the current Cat pointer. The printout
reflects that the starting value is 5 (after all, 5 are constructed), and that
each time the loop is run, one fewer Cat remains.
Note that
HowManyCats is public and is accessed directly by main(). There is no reason to
expose this member variable in this way. It is preferable to make it private
along with the other member variables and provide a public accessor method, as
long as you will always access the data through an instance of Cat. On the
other hand, if you'd like to access this data directly without necessarily
having a Cat object available, you have two options: keep it public, as shown
in Listing 14.2, or provide a static member function, as discussed later in
this chapter.
View Code
1: //Listing 14.2 static data members
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++;
}
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge =
age; }
12: static int HowManyCats;
13:
14: private:
15: int itsAge;
16:
17: };
18:
19: int Cat::HowManyCats = 0;
20:
21: void TelepathicFunction();
22:
23: int main()
24: {
25: const int MaxCats = 5; int i;
26: Cat *CatHouse[MaxCats];
27: for (i = 0; i<MaxCats; i++)
28: {
29: CatHouse[i] = new Cat(i);
30: TelepathicFunction();
31: }
32:
33: for ( i = 0; i<MaxCats; i++)
34: {
35: delete CatHouse[i];
36: TelepathicFunction();
37: }
38: return 0;
39: }
40:
41: void TelepathicFunction()
42: {
43: cout << "There are ";
44: cout << Cat::HowManyCats <<
" cats alive!\n";
45: }
Output:
There are 1 cats alive!
There are
2 cats alive!
There are
3 cats alive!
There are
4 cats alive!
There are
5 cats alive!
There are
4 cats alive!
There are
3 cats alive!
There are
2 cats alive!
There are
1 cats alive!
There are 0 cats alive!
Analysis:
Listing 14.2 is much like Listing 14.1 except for the addition of a new function,
TelepathicFunction(). This function does not create a Cat object, nor does it
take a Cat object as a parameter, yet it can access the HowManyCats member
variable. Again, it is worth reemphasizing that this member variable is not in
any particular object; it is in the class as a whole, and, if public, can be
accessed by any function in the program.
The
alternative to making this member variable public is to make it private. If you
do, you can access it through a member function, but then you must have an
object of that class available. Listing 14.3 shows this approach. The
alternative, static member functions, is discussed immediately after the
analysis of Listing 14.3.
1: //Listing 14.3 private static data members
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++;
}
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge =
age; }
12: virtual int GetHowMany() { return
HowManyCats; }
13:
14:
15: private:
16: int itsAge;
17: static int HowManyCats;
18: };
19:
20: int Cat::HowManyCats = 0;
21:
22: int main()
23: {
24: const int MaxCats = 5; int i;
25: Cat *CatHouse[MaxCats];
26: for (i = 0; i<MaxCats; i++)
27: CatHouse[i] = new Cat(i);
28:
29: for (i = 0; i<MaxCats; i++)
30: {
31: cout << "There are ";
32: cout <<
CatHouse[i]->GetHowMany();
33: cout << " cats
left!\n";
34: cout << "Deleting the one
which is ";
35: cout <<
CatHouse[i]->GetAge()+2;
36: cout << " years
old\n";
37: delete CatHouse[i];
38: CatHouse[i] = 0;
39: }
40: return 0;
41: }
Output:
There are 5 cats left!
Deleting
the one which is 2 years old
There are 4
cats left!
Deleting
the one which is 3 years old
There are 3
cats left!
Deleting
the one which is 4 years old
There are 2
cats left!
Deleting
the one which is 5 years old
There are 1
cats left!
Deleting
the one which is 6 years old
Analysis:
On line 17, the static member variable HowManyCats is declared to have private
access. Now you cannot access this variable from non-member functions, such as
TelepathicFunction from the previous listing.
Even though
HowManyCats is static, it is still within the scope of the class. Any class
function, such as GetHowMany(), can access it, just as member functions can access
any member data. However, for a function to call GetHowMany(), it must have an
object on which to call the function.
--------------------------------------------------------------------------------
DO use
static member variables to share data among all instances of a class. DO make
static member variables protected or private if you wish to restrict access to
them. DON'T use static member variables to store data for one object. Static
member data is shared among all objects of its class.
--------------------------------------------------------------------------------
Static
member functions are like static member variables: they exist not in an object
but in the scope of the class. Thus, they can be called without having an
object of that class, as illustrated in Listing 14.4.
View Code
1: //Listing 14.4 static data members
2:
3: #include <iostream.h>
4:
5: class Cat
6: {
7: public:
8: Cat(int age):itsAge(age){HowManyCats++;
}
9: virtual ~Cat() { HowManyCats--; }
10: virtual int GetAge() { return itsAge; }
11: virtual void SetAge(int age) { itsAge =
age; }
12: static int GetHowMany() { return
HowManyCats; }
13: private:
14: int itsAge;
15: static int HowManyCats;
16: };
17:
18: int Cat::HowManyCats = 0;
19:
20: void TelepathicFunction();
21:
22: int main()
23: {
24: const int MaxCats = 5;
25: Cat *CatHouse[MaxCats]; int i;
26: for (i = 0; i<MaxCats; i++)
27: {
28: CatHouse[i] = new Cat(i);
29: TelepathicFunction();
30: }
31:
32: for ( i = 0; i<MaxCats; i++)
33: {
34: delete CatHouse[i];
35: TelepathicFunction();
36: }
37: return 0;
38: }
39:
40: void TelepathicFunction()
41: {
42: cout << "There are "
<< Cat::GetHowMany() << " cats alive!\n";
43: }
Output:
There are 1 cats alive!
There are
2 cats alive!
There are
3 cats alive!
There are
4 cats alive!
There are
5 cats alive!
There are
4 cats alive!
There are
3 cats alive!
There are
2 cats alive!
There are
1 cats alive!
There are 0 cats alive!
Analysis:
The static member variable HowManyCats is declared to have private access on
line 15 of the Cat declaration. The public accessor function, GetHowMany(), is
declared to be both public and static on line 12.
Since
GetHowMany() is public, it can be accessed by any function, and since it is
static there is no need to have an object of type Cat on which to call it.
Thus, on line 42, the function TelepathicFunction() is able to access the
public static accessor, even though it has no access to a Cat object. Of
course, you could have called GetHowMany() on the Cat objects available in
main(), just as with any other accessor functions.
--------------------------------------------------------------------------------
NOTE:
Static member functions do not have a this pointer. Therefore, they cannot be
declared const. Also, because member data variables are accessed in member
functions using the this pointer, static member functions cannot access any
non-static member variables!
--------------------------------------------------------------------------------
You can
access static member functions by calling them on an object of the class just
as you do any other member function, or you can call them without an object by
fully qualifying the class and object name. Example
class Cat
{
public:
static int
GetHowMany() { return HowManyCats; }
private:
static int
HowManyCats;
};
int
Cat::HowManyCats = 0;
int main()
{
int
howMany;
Cat
theCat; // define a
cat
howMany =
theCat.GetHowMany(); // access through
an object
howMany =
Cat::GetHowMany(); // access without
an object
}
Just as an
array name is a constant pointer to the first element of the array, a function
name is a constant pointer to the function. It is possible to declare a pointer
variable that points to a function, and to invoke the function by using that
pointer. This can be very useful; it allows you to create programs that decide
which functions to invoke based on user input.
The only
tricky part about function pointers is understanding the type of the object
being pointed to. A pointer to int points to an integer variable, and a pointer
to a function must point to a function of the appropriate return type and
signature.
In the
declaration
long (*
funcPtr) (int);
funcPtr is
declared to be a pointer (note the * in front of the name) that points to a
function that takes an integer parameter and returns a long. The parentheses
around * funcPtr are necessary because the parentheses around int bind more
tightly, that is they have higher precedence than the indirection operator (*).
Without the first parentheses this would declare a function that takes an
integer and returns a pointer to a long. (Remember that spaces are meaningless
here.)
Examine
these two declarations:
long *
Function (int);
long (*
funcPtr) (int);
The first,
Function (), is a function taking an integer and returning a pointer to a
variable of type long. The second, funcPtr, is a pointer to a function taking
an integer and returning a variable of type long.
The
declaration of a function pointer will always include the return type and the
parentheses indicating the type of the parameters, if any. Listing 14.5
illustrates the declaration and use of function pointers.
View Code
1: // Listing 14.6 Without function pointers
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(int, int);
10: enum BOOL { FALSE, TRUE };
11:
12: int main()
13: {
14: BOOL fQuit = FALSE;
15: int valOne=1, valTwo=2;
16: int choice;
17: while (fQuit == FALSE)
18: {
19: cout << "(0)Quit (1)Change
Values (2)Square (3)Cube (4)Swap: ";
20: cin >> choice;
21: switch (choice)
22: {
23: case 1:
24: PrintVals(valOne, valTwo);
25: GetVals(valOne, valTwo);
26: PrintVals(valOne, valTwo);
27: break;
28:
29: case 2:
30: PrintVals(valOne, valTwo);
31: Square(valOne,valTwo);
32: PrintVals(valOne, valTwo);
33: break;
34:
35: case 3:
36: PrintVals(valOne, valTwo);
37: Cube(valOne, valTwo);
38: PrintVals(valOne, valTwo);
39: break;
40:
41: case 4:
42: PrintVals(valOne, valTwo);
43: Swap(valOne, valTwo);
44: PrintVals(valOne, valTwo);
45: break;
46:
47: default :
48: fQuit = TRUE;
49: break;
50: }
51:
52: if (fQuit)
53: break;
54: }
55: return 0;
56: }
Output:
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1
x: 1 y: 2
New value
for ValOne: 2
New value
for ValTwo: 3
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y:
27
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y:
27
x: 64 y:
729
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y:
729
x: 729 y:
64
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 0
Analysis:
On lines 5-8, four functions are declared, each with the same return type and
signature, returning void and taking two references to integers.
On line 14,
pFunc is declared to be a pointer to a function that returns void and takes two
integer reference parameters. Any of the previous functions can be pointed to
by pFunc. The user is repeatedly offered the choice of which functions to
invoke, and pFunc is assigned accordingly. On lines 35-36, the current value of
the two integers is printed, the currently assigned function is invoked, and
then the values are printed again.
A pointer
to function is invoked exactly like the functions it points to, except that the
function pointer name is used instead of the function name. Assign a pointer to
function to a specific function by assigning to the function name without the
parentheses. The function name is a constant pointer to the function itself.
Use the pointer to function just as you would the function name. The pointer to
function must agree in return value and signature with the function to which
you assign it. Example
long
(*pFuncOne) (int, int);
long
SomeFunction (int, int);
pFuncOne = SomeFunction;
pFuncOne(5,7);
You
certainly could write the program in Listing 14.5 without function pointers,
but the use of these pointers makes the intent and use of the program explicit:
pick a function from a list, and then invoke it.
Listing
14.6 uses the function prototypes and definitions from Listing 14.5, but the
body of the program does not use a function pointer. Examine the differences
between these two listings.
--------------------------------------------------------------------------------
NOTE: To
compile this program, place lines 41-80 from Listing 14.5 immediately after
line 56.
--------------------------------------------------------------------------------
View Code
1: // Listing 14.6 Without function pointers
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(int, int);
10: enum BOOL { FALSE, TRUE };
11:
12: int main()
13: {
14: BOOL fQuit = FALSE;
15: int valOne=1, valTwo=2;
16: int choice;
17: while (fQuit == FALSE)
18: {
19: cout << "(0)Quit (1)Change
Values (2)Square (3)Cube (4)Swap: ";
20: cin >> choice;
21: switch (choice)
22: {
23: case 1:
24: PrintVals(valOne, valTwo);
25: GetVals(valOne, valTwo);
26: PrintVals(valOne, valTwo);
27: break;
28:
29: case 2:
30: PrintVals(valOne, valTwo);
31: Square(valOne,valTwo);
32: PrintVals(valOne, valTwo);
33: break;
34:
35: case 3:
36: PrintVals(valOne, valTwo);
37: Cube(valOne, valTwo);
38: PrintVals(valOne, valTwo);
39: break;
40:
41: case 4:
42: PrintVals(valOne, valTwo);
43: Swap(valOne, valTwo);
44: PrintVals(valOne, valTwo);
45: break;
46:
47: default :
48: fQuit = TRUE;
49: break;
50: }
51:
52: if (fQuit)
53: break;
54: }
55: return 0;
56: }
Output:
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1
x: 1 y: 2
New value
for ValOne: 2
New value
for ValTwo: 3
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y:
27
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y:
27
x: 64 y:
729
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y:
729
x: 729 y:
64
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 0
Analysis:
The implementation of the functions has been left out, because it is identical
to that provided in Listing 14.5. As you can see, the output is unchanged, but
the body of the program has expanded from 27 lines to 38. The calls to
PrintVals() must be repeated for each case.
It was
tempting to put PrintVals() at the top of the while loop and again at the
bottom, rather than in each case statement. This would have called PrintVals()
even for the exit case, however, and that was not part of the specification.
Setting
aside the increased size of the code and the repeated calls to do the same
thing, the overall clarity is somewhat diminished. This is an artificial case,
however, created to show how pointers to functions work. In real-world
conditions the advantages are even clearer: pointers to functions can eliminate
duplicate code, clarify your program, and allow you to make tables of functions
to call based on runtime conditions.
The pointer
to function does not need to be dereferenced, though you are free to do so.
Therefore, if pFunc is a pointer to a function taking an integer and returning
a variable of type long, and you assign pFunc to a matching function, you can
invoke that function with either
pFunc(x);
or
(*pFunc)(x);
The two
forms are identical. The former is just a shorthand version of the latter.
Just as you
can declare an array of pointers to integers, you can declare an array of
pointers to functions returning a specific value type and with a specific
signature. Listing 14.7 again rewrites Listing 14.5, this time using an array
to invoke all the choices at once.
------------------------------------------------------------------------------
NOTE: To
compile this program, place lines 41-80 of Listing 14.5 immediately after line
39.
--------------------------------------------------------------------------------
View Code
1: // Listing 14.7 demonstrates use of an
array of pointers to functions
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(int, int);
10: enum BOOL { FALSE, TRUE };
11:
12: int main()
13: {
14: int valOne=1, valTwo=2;
15: int choice, i;
16: const MaxArray = 5;
17: void (*pFuncArray[MaxArray])(int&,
int&);
18:
19: for (i=0;i<MaxArray;i++)
20: {
21: cout << "(1)Change Values
(2)Square (3)Cube (4)Swap: ";
22: cin >> choice;
23: switch (choice)
24: {
25: case 1:pFuncArray[i] = GetVals;
break;
26: case 2:pFuncArray[i] = Square;
break;
27: case 3:pFuncArray[i] = Cube;
break;
28: case 4:pFuncArray[i] = Swap;
break;
29: default:pFuncArray[i] = 0;
30: }
31: }
32:
33: for (i=0;i<MaxArray; i++)
34: {
35: pFuncArray[i](valOne,valTwo);
36: PrintVals(valOne,valTwo);
37: }
38: return 0;
39: }
Output:
(1)Change Values (2)Square (3)Cube (4)Swap: 1
(1)Change
Values (2)Square (3)Cube (4)Swap: 2
(1)Change
Values (2)Square (3)Cube (4)Swap: 3
(1)Change
Values (2)Square (3)Cube (4)Swap: 4
(1)Change
Values (2)Square (3)Cube (4)Swap: 2
New Value
for ValOne: 2
New Value
for ValTwo: 3
x: 2 y: 3
x: 4 y: 9
x: 64 y: 729
x: 729 y: 64
x: 7153 y:4096
Analysis:
Once again the implementation of the functions has been left out to save space,
but it is the same as in Listing 14.5. On line 17, the array pFuncArray is de-
clared to be an array of 5 pointers to functions that return void and that take
two integer references.
On lines
19-31, the user is asked to pick the functions to invoke, and each member of
the array is assigned the address of the appropriate function. On lines 33-37,
each function is invoked in turn. The result is printed after each invocation.
The
pointers to functions (and arrays of pointers to functions, for that matter)
can be passed to other functions, which may take action and then call the right
function using the pointer.
For
example, you might improve Listing 14.5 by passing the chosen function pointer
to another function (outside of main()), which prints the values, invokes the
function, and then prints the values again. Listing 14.8 illustrates this
variation.
--------------------------------------------------------------------------------
WARNING: To
compile this program, place lines 46-80 of Listing 14.5 immediately after line
45.
--------------------------------------------------------------------------------
View Code
1: // Listing 14.8 Without function pointers
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: void PrintVals(void (*)(int&,
int&),int&, int&);
10: enum BOOL { FALSE, TRUE };
11:
12: int main()
13: {
14: int valOne=1, valTwo=2;
15: int choice;
16: BOOL fQuit = FALSE;
17:
18: void (*pFunc)(int&, int&);
19:
20: while (fQuit == FALSE)
21: {
22: cout << "(0)Quit (1)Change
Values (2)Square (3)Cube (4)Swap: ";
23: cin >> choice;
24: switch (choice)
25: {
26: case 1:pFunc = GetVals; break;
27: case 2:pFunc = Square; break;
28: case 3:pFunc = Cube; break;
29: case 4:pFunc = Swap; break;
30: default:fQuit = TRUE; break;
31: }
32: if (fQuit == TRUE)
33: break;
34: PrintVals ( pFunc, valOne, valTwo);
35: }
36:
37: return 0;
38: }
39:
40: void PrintVals( void (*pFunc)(int&,
int&),int& x, int& y)
41: {
42: cout << "x: " << x
<< " y: " << y << endl;
43: pFunc(x,y);
44: cout << "x: " << x
<< " y: " << y << endl;
45: }
Output:
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1
x: 1 y: 2
New value
for ValOne: 2
New value
for ValTwo: 3
x: 2 y: 3
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y:
27
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y:
27
x: 64 y:
729
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y:
729
x: 729
y:64
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 0
Analysis:
On line 18, pFunc is declared to be a pointer to a function returning void and
taking two parameters, both integer references. On line 9, PrintVals is
declared to be a function taking three parameters. The first is a pointer to a
function that returns void but takes two integer reference parameters, and the
second and third arguments to PrintVals are integer references. The user is
again prompted for which functions to call, and then on line 34 PrintVals is called.
Go find a
C++ programmer and ask him what this declaration means:
void
PrintVals(void (*)(int&, int&),int&, int&);
This is the
kind of declaration that you use infrequently and probably look up in the book
each time you need it, but it will save your program on those rare occasions
when it is exactly the required construct.
The
construct void (*)(int&, int&) is cumbersome, at best. You can use
typedef to simplify this, by declaring a type VPF as a pointer to a function
returning void and taking two integer references. Listing 14.9 rewrites Listing
14.8 using this typedef statement.
--------------------------------------------------------------------------------
NOTE: To
compile this program, place lines 46-80 of Listing 14.5 immediately after line
45.
--------------------------------------------------------------------------------
View Code
1: // Listing 14.9. Using typedef to make
pointers to functions more _readable
2:
3: #include <iostream.h>
4:
5: void Square (int&,int&);
6: void Cube (int&, int&);
7: void Swap (int&, int &);
8: void GetVals(int&, int&);
9: typedef
void (*VPF) (int&, int&) ;
10: void PrintVals(VPF,int&, int&);
11: enum BOOL { FALSE, TRUE };
12:
13: int main()
14: {
15: int valOne=1, valTwo=2;
16: int choice;
17: BOOL fQuit = FALSE;
18:
19: VPF pFunc;
20:
21: while (fQuit == FALSE)
22: {
23: cout << "(0)Quit (1)Change Values
(2)Square (3)Cube (4)Swap: ";
24: cin >> choice;
25: switch (choice)
26: {
27: case 1:pFunc = GetVals; break;
28: case 2:pFunc = Square; break;
29: case 3:pFunc = Cube; break;
30: case 4:pFunc = Swap; break;
31: default:fQuit = TRUE; break;
32: }
33: if (fQuit == TRUE)
34: break;
35: PrintVals ( pFunc, valOne, valTwo);
36: }
37: return 0;
38: }
39:
40: void PrintVals( VPF pFunc,int& x,
int& y)
41: {
42: cout << "x: " << x
<< " y: " << y << endl;
43: pFunc(x,y);
44: cout << "x: " << x
<< " y: " << y << endl;
45: }
Output:
(0)Quit (1)Change Values (2)Square (3)Cube (4)Swap: 1
x: 1 y: 2
New value
for ValOne: 2
New value
for ValTwo: 3
x: 2 y: 3
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 3
x: 2 y: 3
x: 8 y:
27
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 2
x: 8 y:
27
x: 64 y:
729
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 4
x: 64 y:
729
x: 729 y:
64
(0)Quit
(1)Change Values (2)Square (3)Cube (4)Swap: 0
Analysis:
On line 9, typedef is used to declare VPF to be of the type "function that
returns void and takes two parameters, both integer references."
On line 10,
the function PrintVals() is declared to take three parameters: a VPF and two
integer references. On line 19, pFunc is now declared to be of type VPF.
Once the
type VPF is defined, all subsequent uses to declare pFunc and PrintVals() are
much cleaner. As you can see, the output is identical.
Pointers to
Member Functions
Up until
this point, all of the function pointers you've created have been for general,
non-class functions. It is also possible to create pointers to functions that
are members of classes.
To create a
pointer to member function, use the same syntax as with a pointer to function,
but include the class name and the scoping operator (::). Thus, if pFunc points
to a member function of the class Shape, which takes two integers and returns
void, the declaration for pFunc is the following:
void
(Shape::*pFunc) (int, int);
Pointers to
member functions are used in exactly the same way as pointers to functions,
except that they require an object of the correct class on which to invoke
them. Listing 14.10 illustrates the use of pointers to member functions.
View Code
1: //Listing 14.10 Pointers to member
functions using virtual methods
2:
3: #include <iostream.h>
4:
5: enum BOOL {FALSE, TRUE};
6: class Mammal
7: {
8: public:
9: Mammal():itsAge(1) { }
10: ~Mammal() { }
11: virtual void Speak() const = 0;
12: virtual void Move() const = 0;
13: protected:
14: int itsAge;
15: };
16:
17: class Dog : public Mammal
18: {
19: public:
20: void Speak()const { cout <<
"Woof!\n"; }
21: void Move() const { cout <<
"Walking to heel...\n"; }
22: };
23:
24:
25: class Cat : public Mammal
26: {
27: public:
28: void Speak()const { cout <<
"Meow!\n"; }
29: void Move() const { cout <<
"slinking...\n"; }
30: };
31:
32:
33: class Horse : public Mammal
34: {
35: public:
36: void Speak()const { cout <<
"Winnie!\n"; }
37: void Move() const { cout <<
"Galloping...\n"; }
38: };
39:
40:
41: int main()
42: {
43: void (Mammal::*pFunc)() const =0;
44: Mammal* ptr =0;
45: int Animal;
46: int Method;
47: BOOL fQuit = FALSE;
48:
49: while (fQuit == FALSE)
50: {
51: cout << "(0)Quit (1)dog
(2)cat (3)horse: ";
52: cin >> Animal;
53: switch (Animal)
54: {
55: case 1: ptr = new Dog; break;
56: case 2: ptr = new Cat; break;
57: case 3: ptr = new Horse; break;
58: default: fQuit = TRUE; break;
59: }
60: if (fQuit)
61: break;
62:
63: cout << "(1)Speak (2)Move: ";
64: cin >> Method;
65: switch (Method)
66: {
67: case 1: pFunc = Mammal::Speak;
break;
68: default: pFunc = Mammal::Move;
break;
69: }
70:
71: (ptr->*pFunc)();
72: delete ptr;
73: }
74: return 0;
75: }
Output:
(0)Quit (1)dog (2)cat (3)horse: 1
(1)Speak
(2)Move: 1
Woof!
(0)Quit
(1)dog (2)cat (3)horse: 2
(1)Speak
(2)Move: 1
Meow!
(0)Quit
(1)dog (2)cat (3)horse: 3
(1)Speak
(2)Move: 2
Galloping
(0)Quit
(1)dog (2)cat (3)horse: 0
Analysis:
On lines 6-15, the abstract data type Mammal is declared with two pure virtual
methods, Speak() and Move(). Mammal is subclassed into Dog, Cat, and Horse,
each of which overrides Speak() and Move().
The driver
program in main() asks the user to choose which type of animal to create, and
then a new subclass of Animal is created on the free store and assigned to ptr
on lines 55-57.
The user is
then prompted for which method to invoke, and that method is assigned to the
pointer pFunc. On line 71, the method chosen is invoked by the object created,
by using the pointer ptr to access the object and pFunc to access the function.
Finally, on
line 72, delete is called on the pointer ptr to return the memory set aside for
the object to the free store. Note that there is no reason to call delete on
pFunc because this is a pointer to code, not to an object on the free store. In
fact, attempting to do so will generate a compile-time error.
As with
pointers to functions, pointers to member functions can be stored in an array.
The array can be initialized with the addresses of various member functions,
and these can be invoked by offsets into the array. Listing 14.11 illustrates
this technique.
View Code
1: //Listing 14.11 Array of pointers to
member functions
2:
3: #include <iostream.h>
4:
5: enum BOOL {FALSE, TRUE};
6:
7: class Dog
8: {
9: public:
10: void Speak()const { cout <<
"Woof!\n"; }
11: void Move() const { cout <<
"Walking to heel...\n"; }
12: void Eat() const { cout <<
"Gobbling food...\n"; }
13: void Growl() const { cout <<
"Grrrrr\n"; }
14: void Whimper() const { cout <<
"Whining noises...\n"; }
15: void RollOver() const { cout <<
"Rolling over...\n"; }
16: void PlayDead() const { cout <<
"Is this the end of Little Caeser?\n"; }
17: };
18:
19: typedef void (Dog::*PDF)()const ;
20: int main()
21: {
22: const int MaxFuncs = 7;
23: PDF DogFunctions[MaxFuncs] =
24: {
Dog::Speak,
25: Dog::Move,
26: Dog::Eat,
27: Dog::Growl,
28: Dog::Whimper,
29: Dog::RollOver,
30: Dog::PlayDead };
31:
32: Dog* pDog =0;
33: int Method;
34: BOOL fQuit = FALSE;
35:
36: while (!fQuit)
37: {
38: cout << "(0)Quit (1)Speak (2)Move (3)Eat
(4)Growl";
39: cout << " (5)Whimper (6)Roll
Over (7)Play Dead: ";
40: cin >> Method;
41: if (Method == 0)
42: {
43: fQuit = TRUE;
44: break;
45: }
46: else
47: {
48: pDog = new Dog;
49: (pDog->*DogFunctions[Method-1])();
50: delete pDog;
51: }
52: }
53: return 0;
54: }
Output: (0)Quit (1)Speak (2)Move (3)Eat
(4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 1
Woof!
(0)Quit (1)Speak (2)Move (3)Eat (4)Growl
(5)Whimper (6)Roll Over (7)Play Dead: 4
Grrr
(0)Quit (1)Speak (2)Move (3)Eat (4)Growl
(5)Whimper (6)Roll Over (7)Play Dead: 7
Is this
the end of Little Caeser?
(0)Quit (1)Speak (2)Move (3)Eat (4)Growl
(5)Whimper (6)Roll Over (7)Play Dead: 0
Analysis:
On lines 7-17, the class Dog is created, with 7 member functions all sharing
the same return type and signature. On line 19, a typedef declares PDF to be a
pointer to a member function of Dog that takes no parameters and returns no
values, and that is const: the signature of the 7 member functions of Dog.
On lines
23-30, the array DogFunctions is declared to hold 7 such member functions, and
it is initialized with the addresses of these functions.
On lines 38
and 39, the user is prompted to pick a method. Unless they pick Quit, a new Dog
is created on the heap, and then the correct method is invoked on the array on
line 49. Here's another good line to show to the hotshot C++ programmers in
your company; ask them what this does:
(pDog->*DogFunctions[Method-1])();
Once again,
this is a bit esoteric, but when you need a table built from member functions,
it can make your program far easier to read and understand.
--------------------------------------------------------------------------------
DO invoke
pointers to member functions on a specific object of a class. DO use typedef to
make pointer to member function declarations easier to read. DON'T use pointer
to member functions when there are simpler solutions.
--------------------------------------------------------------------------------
Today you
learned how to create static member variables in your class. Each class, rather
than each object, has one instance of the static member variable. It is
possible to access this member variable without an object of the class type by
fully qualifying the name, assuming you've declared the static member to have
public access.
Static
member variables can be used as counters across instances of the class. Because
they are not part of the object, the declaration of static member variables
does not allocate memory, and static member variables must be defined and
initialized outside the declaration of the class.
Static
member functions are part of the class in the same way that static member
variables are. They can be accessed without a particular object of the class,
and can be used to access static member data. Static member functions cannot be
used to access non-static member data because they do not have a this pointer.
Because
static member functions do not have a this pointer, they also cannot be made
const. const in a member function indicates that the this pointer is const.
You also
learned how to declare and use pointers to functions and pointers to member
functions. You saw how to create arrays of these pointers and how to pass them
to functions.
Pointers to
functions and pointers to member functions can be used to create tables of
functions that can be selected from at runtime. This can give your program
flexibility that is not easily achieved without these pointers.
Q. Why use
static data when you can use global data?
A. Static
data is scoped to the class. In this manner, static data are available only
through an object of the class, through an explicit call using the class name
if they are public, or by using a static member function. Static data are typed
to the class type, however, and the restricted access and strong typing makes
static data safer than global data.
Q. Why use
static member functions when you can use global functions?
A. Static
member functions are scoped to the class, and can be called only by using an
object of the class or an explicit full specification (such as
ClassName::FunctionName()).
Q. Is it
common to use many pointers to functions and pointers to member functions?
A. No,
these have their special uses, but are not common constructs. Many complex and
powerful programs have neither.
The
Workshop contains quiz questions to help solidify your understanding of the
material covered and exercises to provide you with experience in using what
you've learned. Try to answer the quiz and exercise questions before checking
the answers in Appendix D, and make sure you understand the answers before
going to the next chapter.
1. Can
static member variables be private?
2. Show the
declaration for a static member variable.
3. Show the
declaration for a static function pointer.
4. Show the
declaration for a pointer to function returning long and taking an integer
parameter.
5. Modify
the pointer in Question 4 so it's a pointer to member function of class Car.
6. Show the
declaration for an array of 10 pointers as defined in Question 5.
Exercises
1. Write a
short program declaring a class with one member variable and one static member
variable. Have the constructor initialize the member variable and increment the
static member variable. Have the destructor decrement the member variable.
2. Using
the program from Exercise 1, write a short driver program that makes three
objects and then displays their member variables and the static member
variable. Then
destroy
each object and show the effect on the static member variable.
3. Modify
the program from Exercise 2 to use a static member function to access the
static member variable. Make the static member variable private.
4. Write a
pointer to member function to access the non-static member data in the program
in Exercise 3, and use that pointer to print the value of that data.
5. Add two
more member variables to the class from the previous questions. Add accessor
functions that get the value of these values, and give all the member functions
the same return values and signatures. Use the pointer to member function to
access these functions.