Some of the member functions of the generic collection classes require a pointer to a user-defined function. There are two kinds of these user-defined functions, discussed in the following two sections.
The first kind of user-defined function is a tester function. It has the form:
RWBoolean tester(const type* ty, const void* a)
where tester is the name of the function, type is the type of the members of the collection class, and RWBoolean is a typedef for an int whose only possible values are TRUE or FALSE. The job of the tester function is to signal when a certain member of the collection has been identified. The decision of how this is done, or what it means to have identified an object, is left to the user. You can choose to compare addresses (test for two objects being identical), or to look for certain values within the object (test for isEqual). The first variable ty, which can be thought of as a candidate, will point to a member of the collection. The second variable a, which can be thought of as client data, can be tested against ty for a match.
In the following example, which expands on the previous one, the problem is to test for isEqual. We push some values onto a stack to see if a certain value exists on the stack. The member function contains() of class RWGStack(type) has prototype:
RWBoolean contains(RWBoolean (*t)(const type*, const void*), const void* a) const;
The first argument is RWBoolean (*t)(const type*, const void*). This is a pointer to the tester function, for which we will have to provide an appropriate definition:
#include <rw/gstack.h> #include <rw/rstream.h> declare(RWGStack, int) RWBoolean myTesterFunction(const int* jp, const void* a) //1 { return *jp == *(const int*)a; //2 } main(){ RWGStack(int) gs; //3 gs.push(new int(1)); //4 gs.push(new int(2)); //5 gs.push(new int(3)); //6 gs.push(new int(4)); //7 int aValue = 2; //8 if ( gs.contains(myTesterFunction, &aValue) ) //9 cout << "Yup.\n"; else cout << "Nope.\n"; while(!gs.isEmpty()) delete gs.pop(); return 0; }
Program Output:
Yup.
A description of each program line follows.
//1 | This is the tester function. Note that the first argument is a pointer to the type of objects in the collection, ints in this case. The second argument points to an object that can be of any type_also an int in this example. Note that both arguments are declared const pointers; in general, the tester function should not change the value of the objects being pointed to. |
//2 | The second argument is converted from a const void* to a const int*, then dereferenced. The result is a const int. This const int is compared to the dereferenced first argument, which is also a const int. The net result is that this tester function considers a match to occur when the two ints have the same values (i.e., they are equal). Note that we could also choose to identify a particular int (i.e., test for identity). |
//3-//7 | These lines are the same as in the example in Section 12.1. A generic stack of (pointers to) ints is declared and defined, then 4 values are pushed onto it. |
//8 | This is the value (i.e., 2) that we will look for in the stack. |
//9 | Here the member function contains() is called, using the tester function. The second argument of contains(), a pointer to the variable aValue, will appear as the second argument of the tester function. The function contains() traverses the entire stack, calling the tester function for each item in turn, and waiting for the tester function to signal a match. If it does, contains() returns TRUE; otherwise, FALSE. |
Note that the second argument of the tester function does not necessarily have to be of the same type as the members of the collection, although it is in the example above. In the following example, the argument and members of the collection are of different types:
#include <rw/gstack.h> #include <rw/rstream.h> class Foo { public: int data; Foo(int i) {data = i;} }; declare(RWGStack, Foo) // A stack of pointers to Foos RWBoolean anotherTesterFunction(const Foo* fp, const void* a) { return fp->data == *(const int*)a; } main(){ RWGStack(Foo) gs; gs.push(new Foo(1)); gs.push(new Foo(2)); gs.push(new Foo(3)); gs.push(new Foo(4)); int aValue = 2; if ( gs.contains(anotherTesterFunction, &aValue) ) cout << "Yup.\n"; else cout << "Nope.\n"; while(!gs.isEmpty()) delete gs.pop(); return 0; }
In this example, a stack of (pointers to) Foos is declared and used, while the variable being passed as the second argument to the tester function is still a const int*. The tester function must take the different types into account.
The second kind of user-defined function is an apply function. Its general form is:
void yourApplyFunction(type* ty, void* a)
where yourApplyFunction is the name of the function, and type is the type of the members of the collection. Apply functions give you the opportunity to perform some operation on each member of a collection, perhaps print it out or draw it on a screen. The second argument is designed to hold client data to be used by the function, perhaps the handle of a window on which the object is to be drawn.
In the following example, the apply-function printAFoo is used to print out the value of each member in RWGDlist(type), a generic doubly-linked list:
#include <rw/gdlist.h> #include <rw/rstream.h> class Foo { public: int val; Foo(int i) {val = i;} }; declare(RWGDlist, Foo) void printAFoo(Foo* ty, void* sp){ ostream* s = (ostream*)sp; (*s) << ty->val << "\n"; } main(){ RWGDlist(Foo) gd; gd.append(new Foo(1)); gd.append(new Foo(2)); gd.append(new Foo(3)); gd.append(new Foo(4)); gd.apply(printAFoo, &cout); while(!gd.isEmpty()) delete gd.get(); return 0; }
Program Output:
1 2 3 4
The items are appended at the tail of the list. For each item, the function apply()calls the user-defined function printAFoo() with the address of the item as the first argument, and the address of an ostream (an output stream) as the second argument. The job of printAFoo() is to print out the value of member data val. Because apply() scans the list from beginning to end, the items will come out in the same order in which they were inserted. See the Class Reference for RWGDlist(type).
With some care, you can use apply functions to change the objects in a collection. For example, you could use an apply function to change the value of member data val in the example above. However you cannot use apply() to change the number or location of items within a collection. In particular, your apply function must not remove any item from, nor add any item to, the collection.