Day 15. Special Classes and Functions

C++ offers several 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 will learn

• How to share information across objects of the same type

• 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

Sharing Data Among Objects of the Same Type: Static Member Data

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. If you have five Cat objects, for example, each has its own age, weight, and other data. The age of one does not affect the age of another.

At times, however, you’ll want to keep track of data that applies to all objects of the same type. 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 variables that 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 15.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.

Listing 15.1. Static Member Data

Image

Image


There are 5 cats left!
Deleting the one that is 0 years old
There are 4 cats left!
Deleting the one that is 1 years old
There are 3 cats left!
Deleting the one that is 2 years old
There are 2 cats left!
Deleting the one that is 3 years old
There are 1 cats left!
Deleting the one that is 4 years old

Image

On lines 5–16, 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 nonstatic 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 18, the variable is defined and initialized.

It is a common mistake to forget to declare static member variables and then to forget to define them. 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 nonstatic member variable and is defined each time you make a Cat object, which you do here on line 26.

On line 8, the constructor for Cat increments the static member variable. 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 20–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 on line 36. 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 for you to expose this member variable in this way. In fact, 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 want to access this data directly, without necessarily having a Cat object available, you have two options: Keep it public, as shown in Listing 15.2, or provide a static member function, as discussed later in today’s lesson.

Listing 15.2. Accessing Static Members Without an Object

Image

Image


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!

Image

Listing 15.2 is very much like Listing 15.1 except for the addition of a new function, TelepathicFunction(). This function, which is defined on lines 41–45, 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, where it is accessible to any member function. If public, this variable can be accessed by any function in the program, even when that function does not have an instance of a class.

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 15.3 shows this approach. You’ll learn an alternative to this access—using static member functions—immediately after the analysis of Listing 15.3.

Listing 15.3. Accessing Static Members Using Nonstatic Member Functions

Image

Image


There are 5 cats left!
Deleting the one that is 2 years old
There are 4 cats left!
Deleting the one that is 3 years old
There are 3 cats left!
Deleting the one that is 4 years old
There are 2 cats left!
Deleting the one that is 5 years old
There are 1 cats left!
Deleting the one that is 6 years old

Image

On line 16, the static member variable HowManyCats is declared to have private access. Now, you cannot access this variable from nonmember functions, such as TelepathicFunction() from the previous listing.

Even though HowManyCats is static, it is still within the scope of the class. As such, any class function, such as GetHowMany(), can access it, just as member functions can access any member data. However, for a function outside of a Cat object to call GetHowMany(), it must have a Cat object on which to call the function.

Image

Using Static Member Functions

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

Listing 15.4. Static Member Functions

Image

Image


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!

Image

The static member variable HowManyCats is declared to have private access on line 14 of the Cat declaration. The public accessor function, GetHowMany(), is declared to be both public and static on line 11.

Because GetHowMany() is public, it can be accessed by any function, and because it is static, no need exists to have an object of type Cat on which to call it. Thus, on line 41, the function TelepathicFunction() is able to access the public static accessor, even though it has no access to a Cat object. You should note, however, that the function is fully qualified when it is called, meaning the function call is prefixed with the class name followed by two colons:


Cat::TelepathicFunction()

Of course, you could also have called GetHowMany() on the Cat objects available in main(), the same 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 nonstatic member variables!

Static Member Functions

You can access static member functions by calling them on an object of the class the same as you do any other member function, or you can call them without an object by fully qualifying the class and object name.

Example

Image

Pointers to Functions

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 enables 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 15.5 illustrates the declaration and use of function pointers.

Listing 15.5. Pointers to Functions

Image

Image

Image

Image


(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

Image

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 13, pFunc is declared to be a pointer to a function that returns void and takes two integer reference parameters. Because the signatures match, 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 33–35, the current value of the two integers is printed, the currently assigned function is invoked, and then the values are printed again.

Pointer to Function

A pointer to function is invoked the same as 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 the same 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);

Caution

Be aware that pointers to functions can be highly dangerous. You can accidentally assign to a function pointer when you want to call the function, or you can accidentally call the function when you want to assign to its pointer.

Why Use Function Pointers?

Generally, you shouldn’t use function pointers. Function pointers date from the days of C, before object-oriented programming was available. They were provided to allow for a programming style that had some of the virtues of object orientation; however, if you are writing a program that is highly dynamic and needs to operate different functionality based on runtime decisions, this can be a viable solution.

You certainly could write a program like Listing 15.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 15.6 uses the function prototypes and definitions from Listing 15.5, but the body of the program does not use a function pointer. Examine the differences between these two listings.

Listing 15.6. Rewriting Listing 15.5 Without the Pointer to Function

Image

Image

Image


(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

Image

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 a program, and enable tables of functions that can be called based on runtime conditions.

Tip

Object-oriented programming should generally allow you to avoid the need to create or pass pointers to functions. Instead, call the desired function on the desired object or the desired static member function on the class. If you need an array of function pointers, ask yourself whether what you really need is an array of appropriate objects.

Shorthand Invocation

The pointer to function does not need to be dereferenced, although 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.

Arrays of Pointers to Functions

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 15.7 again rewrites Listing 15.5, this time using an array to invoke all the choices at once.

Listing 15.7. Demonstrates Use of an Array of Pointers to Functions

Image

Image

Image

Image


(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: 531441 y:4096

Image

On line 17, the array pFuncArray is declared to be an array of five 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–39, each function is invoked in turn. The result is printed after each invocation.

Passing Pointers to Functions to Other Functions

The pointers to functions (and arrays of pointers to functions, for that matter) can be passed to other functions, which can take action and then call the right function using the pointer.

You might improve Listing 15.5, for example, 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 15.8 illustrates this variation.

Listing 15.8. Passing Pointers to Functions as Function Arguments

Image

Image

Image


(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

Image

On line 17, 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 on lines 19 and 20 for which functions to call, and then on line 33 PrintVals is called using the function pointer, pFunc, as the first parameter.

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.

Using typedef with Pointers to Functions

The construct void (*)(int&, int&) is cumbersome, at best. You can use typedef to simplify this, by declaring a type (in Listing 15.9, it is called VPF) as a pointer to a function returning void and taking two integer references. Listing 15.9 rewrites Listing 15.8 using this typedef statement.

Listing 15.9. Using typedef to Make Pointers to Functions More Readable

Image

Image

Image


(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

Image

On line 10, typedef is used to declare VPF to be of the type “pointer to function that returns void and takes two parameters, both integer references.”

On line 11, 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.

After the type VPF is defined, all subsequent uses to declare pFunc and PrintVals() are much cleaner. As you can see, the output is identical. Remember, a typedef primarily does a replacement. In this case, using the typedef makes your code much easier to follow.

Pointers to Member Functions

Up until this point, all the function pointers you’ve created have been for general, nonclass functions. It is also possible to create pointers to functions that are members of classes. This is a highly advanced and infrequently used technique that should be avoided whenever possible. It is, however, important to understand this technique as some people do choose to use it.

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 the same way as pointers to functions, except that they require an object of the correct class on which to invoke them. Listing 15.10 illustrates the use of pointers to member functions.

Listing 15.10. Pointers to Member Functions

Image

Image

Image


(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

Image

On lines 5–14, the abstract class 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() starts on line 40. On line 50, the user is asked to choose the type of animal to create. Based on this selection, a new subclass of Animal is created on the free store and assigned to ptr on lines 54–56.

On line 61, the user is given a second prompt asking him to select the method to invoke. The method selected, either Speak or Move, is assigned to the pointer pFunc on lines 65–66. On line 69, 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 70, delete is called on the pointer ptr to return the memory set aside for the object to the free store. Note that no reason exists 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 generates a compile-time error.

Arrays of Pointers to Member Functions

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 15.11 illustrates this technique.

Listing 15.11. Array of Pointers to Member Functions

Image

Image

Image


(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
The end of Little Caesar?
(0)Quit (1)Speak (2)Move (3)Eat (4)Growl (5)Whimper (6)Roll Over (7)Play Dead: 0

Image

On lines 5–15, the class Dog is created, with seven member functions all sharing the same return type and signature. On line 17, 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 seven member functions of Dog.

On lines 21–28, the array DogFunctions is declared to hold seven such member functions, and it is initialized with the addresses of these functions.

On lines 36 and 37, the user is prompted to pick a method. Unless Quit is picked, a new Dog is created on the heap, and then the correct method is invoked on the array on line 46. Here’s another good line to show to the hotshot C++ programmers in your company; ask them what this does:


(pDog->*DogFunctions[Method-1])();

You’ll be able to tell the hotshot that it is a call to a method in an object using a pointer stored to the method that is stored in an array at the offset of Method-1.

Once again, this is a technique that should be avoided whenever possible. If it must be used, document it extensively and try to think of another way to accomplish the desired task.

Image

Summary

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.

You learned that one use of static member variables is 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 nonstatic member data because they do not have the 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 this is const.

Today’s lesson also included one of the more complex topics in C++. You 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, and how to call the functions whose pointers were stored in this way. You learned that this is not really a great idea, and that object-oriented techniques should allow you to avoid this in almost every situation.

Q&A

Q   Why use static data when I can use global data?

A   Static data is scoped to the class. In this manner, static data is 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 is 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 I 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. There might, however, be times when these offer the only solution.

Workshop

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 be certain you understand the answers before going to tomorrow’s lesson.

Quiz

1. Can static member variables be private?

2. Show the declaration for a static member variable called itsStatic that is of type int.

3. Show the declaration for a static function called SomeFunction that returns an integer and takes no parameters.

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 called theArray that contains 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 nonstatic 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 exercises. Add accessor functions that get the value of this data and give all the member functions the same return values and signatures. Use the pointer to member function to access these functions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.14.251.57