A functor map is both a container that holds functors and a functor itself. The container is a map, or registration, of functors and associated keys through which they are accessed. The map itself is a part of the functor hierarchy, and it implements the entire functor API, so it can be passed where a functor is expected.
In fact, the code to which the functor map is passed need not know that it is not a basic functor. The caller code invokes what it believes to be a simple functor, using the simple functor's API, and the map handles the complexity of finding the correct functor within the map and invoking it.
As an example, imagine an application that draws various shapes on the screen, depending on some input from the user. A functor is set up to create each shape. Each functor is added to a functor map with its key, in this case, a string containing the name of the shape. When an application user requests a certain shape be drawn, the application invokes the functor map with the name of the shape (which serves as the key). Within the functor map, the lookup is performed, and the correct functor is invoked, creating the shape and drawing it on the screen.
As the above example hints, functor maps provide another way to implement the factory pattern, as an alternative to the Factory Pattern package in RW-Enterprise. You can encapsulate creator functions in functors and add them to a functor map with a string representing the name of the object to be created, thus producing a factory. An invocation of the map with the name of a class creates an object of that type. Functor maps can even handle a request to create an object that isn't in the map, by creating some default object such as a parent class object.
Like basic functors, functor maps are implemented by a number of template classes. Depending on the number of arguments passed at invocation time, different functor maps are required.
The map itself is a collection of unique keys and their associated functors. The key is a templatized parameter, so it can be of any type, as long as it has an appropriate hash function. The functor can be any one of the handle classes provided in the Functor package, or any class derived from these classes, such as a functor list or even a functor map.
Because the invocation of the map could invoke any of its functors, all of the functors in a given map must have the same invocation signature and either share the same functor handle type or be derived from the same functor handle type.
Because one of the arguments to the functor map invocation must be the key used for look-up, the functors stored in a functor map have one less argument than the map itself. For example, an RWTFunctorMap2 is invoked with two arguments, a key and another argument that is passed to the RWTFunctor1 associated with the key.
Like functors, functor maps use the handle body idiom. Because each functor map handle can point to exactly one type of functor map, however, the body is created automatically as a part of the handle's construction. Copying functor maps follows the general rules discussed in Section 7.3.1.4, "Handle-body Mechanics." With this technique, the copy constructor and assignment operators reference the old object and, as a result, are very fast.
NOTE: After the assignment, changes to either FunctorMap object affect the other one.
The Functor Map subpackage is made up of a number of template classes designed to accommodate functor arguments of different numbers and types. Functor map classes are an extension to the functor class hierarchy, as shown in Figure 52.
Class names are constructed from the base name RWTFunctorMap by adding 1 or 2 for the number of caller arguments and an optional R if the map returns a value. Because every functor map invocation requires a key, there is no RWTFunctorMap0.
The formal template parameters (shown in the hierarchy) include:
SR - The type of the functor map's return value
Key - The type of the functor map's key
S1 - The type of the encapsulated functor's first argument (which is the functor map's second argument)
The specific type of functor map that you need depends on the type of functors to be held within the map. All functors in a map must be of the same handle type or be derived from the same handle type. The functors must take one less argument at invocation than the map itself.
If you are starting with a function that you wish to wrap, see Section 8.4.1, "Analyzing Functor Requirements," to select a functor type. This choice determines the type of map that you need.
To construct a functor map, you need to know:
What type of map to create.
This type depends on the functor type.
What type of key to associate with the functor.
This type depends on your application. It can be any type for which you can provide a hash function.
The hash function to use for distributing the functors across the map.
The hash function is used for, and therefore depends on, the type of key.
The size of the map to create.
The appropriate size depends on how many functors you expect to store in the map. The default is 61.
NOTE: Try to start with an adequate map size. Changing the size of an existing map is very expensive, because it recreates the entire map.
The following steps show how to create a functor map.
Choose the function you want to wrap, for example:
int setFlag(short s) { flag = s; cout << "flag was set to " << s << endl; return s;}
Create the corresponding functor:
RWTFunctorR1<int,short> setFlagFunctor = rwtMakeFunctorR1( (int(*)(short))NULL, setFlag);
Create the map. If you choose RWCString as the key type, the map construction would look like this:
RWTFunctorMapR2<int,RWCString,short> fmap(RWCString::hash, 4);
At this point, you have a complete but empty functor map instance. Next, you add functors to the map.
fmap.add("setFlag", setFlagFunctor);
The map now has one entry, whose key is the RWCString "setFlag" and whose functor is the setFlagFunctor created earlier.
Once the map contains an entry, you can invoke it. Invocation consists of finding the entry in the map with the matching key and invoking that functor.
int return_value = fmap("setFlag", 32);
This instruction selects the functor setFlagFunctor from the map and invokes the functor with the caller argument 32. The variable return_value is set to 32. The functor invocation produces this output:
Flag was set to 32
If you call the map with a key that has not been registered
int return_value = fmap("some_other_key", 45);
it throws an RWLogicError exception. This may or may not be what the application needs. To handle this case more gracefully, functor maps have the notion of a default functor.
Such a functor is used when a user attempts to invoke or query the map with an invalid key. The default functor can be a more specific error reporting mechanism, such as a pop-up window telling the application user that the input was invalid, or it might cause a less specific but still useful action, such as creating a parent object instead of a more derived type.
Returning to the example, suppose that when the user passes an invalid key, you want to set the flag to 0 and issue a warning that a matching key was not found. To create this behavior, follow these steps:
Create a function with appropriate parameters. (Remember that all of a functor map's entries must be the same functor handle type).
int reportError(short s){ flag = 0; cout << "Flag set to 0, key not found." << endl; return 0;}
Create a functor from the function.
RWTFunctorR1<int,short> errorFunctor = rwtMakeFunctorR1( (int(*)(short))NULL, reportError);
Set that functor as the map's default functor.
fmap.setDefault( errorFunctor );
From this point on, if the user invokes the map with an invalid key, the errorFunctor is invoked.
fmap("unknown", 43);
In this case, the return_value is set to 0, producing this output:
Flag set to 0, key not found.
Later, you can check what the default functor has been set to, using getDefault(). This function returns the default functor, if a default is set, or else throws an RWLogicError exception.
RWTFunctorR1<int,short> defaultFunctor; try{ defaultFunctor = fmap.getDefault(); // If we got here, defaultFunctor now contains the default // functor. } catch( RWLogicError){ //If we got here, there was no default functor set. }
The contents of a functor map can be dynamic, perhaps corresponding to the set of currently available actions, which change over time. When you need to remove entries from the map, use the remove() function:
RWBoolean removed_successfully = fmap.remove("setFlag");
Now setFlagFunctor is removed from the map and removed_successfully is set to TRUE.
To remove all entries in the map at once, use the clear() function:
fmap.clear();
An application encountering a functor map might need some information about it, such as the number of entries or whether a certain key has been added. Below are examples of such queries, which use member functions supplied by the functor map classes. These examples assume that the setFlagFunctor entry is back in the functor map.
To check for a particular key in a map, use the contains() function.
RWBoolean contains_setFlag = fmap.contains("setFlag");
The above code sets the contains_setFlag variable to TRUE, while the following sets the contains_bogusKey variable to FALSE.
RWBoolean contains_bogusKey = fmap.contains("bogusKey");
To check the map for a particular key and also see the matching functor, use the find() function. Because you are asking for two pieces of information, two mechanisms are needed for returning information. The success of the search is returned as the return value, and the matching functor is returned in the reference argument.
RWTFunctorR1<int,short> matching_functor; RWBoolean found = fmap.find("setFlag", matching_functor);
Here found is set to TRUE, because there is a functor in the map with the associated key "setFlag", and matching_functor is set to the setFlagFunctor.
If you ask for a key that is not in the map, the return value is FALSE. However, if the map has a default functor (remember, they are not required), then the default functor is returned in the reference argument matching_functor. This is what happens in the case below.
RWBoolean found = fmap.find("bogusKey", matching_functor);
If the map does not have a default functor, calling the find function with an invalid key throws an RWLogicError exception.
To find the total number of entries in the map, use the entries() function. The default functor is not considered an entry in the map. Thus, in the example, this call sets current_size to 1.
Size_t current_size = fmap.entries();
This example demonstrates the use of a functor map. The complete example is available in examples\thr0200osfam\functor\map\accounts.cpp
#include <rw/functor/functorR1.h> // for all of the RWTFunctorR1s #include <rw/functor/map/RWTFunctorMapR2.h> // for the RWTFunctorMapR2s #include <rw/cstring.h> // for RWCString ... // These classes build very simple representations of various // types of accounts. class BankAccount{ public: BankAccount(unsigned long acct); float deposit(float ammt); float withdraw(float ammt); }; class CreditAccount{ public: CreditAccount(unsigned long acct, float lim); float charge(float ammt); float payment(float ammt); }; float errorFunc(float f) { cout << "Error! request not recognized" << endl; return 0;} int main () { // Because the accounts are stored inside of the individual // functors, in association with particular methods, you can // wrap several different account types and actions in a // single functor map. BankAccount myBankAcct(999999); CreditAccount myCreditAcct(00000, 10000); // Create new RWTFunctorR1s to be the values in the map. RWTFunctorR1<float,float> depositFunctor=rwtMakeFunctorR1( (float(*)(float))NULL, myBankAcct, &BankAccount::deposit); RWTFunctorR1<float,float> withdrawFunctor=rwtMakeFunctorR1( (float(*)(float))NULL, myBankAcct, &BankAccount::withdraw); RWTFunctorR1<float,float> chargeFunctor=rwtMakeFunctorR1( (float(*)(float))NULL, myCreditAcct, &CreditAccount::charge); RWTFunctorR1<float,float> paymentFunctor=rwtMakeFunctorR1( (float(*)(float))NULL, myCreditAcct, &CreditAccount::payment); RWTFunctorR1<float,float> errorFunctor=rwtMakeFunctorR1( (float(*)(float))NULL, errorFunc); // RWCStrings are convenient keys; they even provide their own // hash function. RWCString withdraw = "withdraw"; RWCString deposit = "deposit"; RWCString charge = "charge"; RWCString payment = "payment"; // Declare a new functor map. A complete, but empty, instance // now exists. RWTFunctorMapR2<float,RWCString,float> inqueries(RWCString::hash); // Add each of the functors to the map with its corresponding // key. inqueries.add(deposit, depositFunctor); inqueries.add(withdraw, withdrawFunctor); inqueries.add(charge, chargeFunctor); inqueries.add(payment, paymentFunctor); inqueries.setDefault(errorFunctor); // A crude UI to demonstrate the use of the functor map. cout << "Welcome to your account maintenance program." << endl; cout << "Currently the bank account is empty, and the credit account has a $10,000 limit" << endl; cout << endl << ">> " ; RWCString request; float ammt = 0; float balance = 0; cin >> request; // Read in a request from the user. while( request != "quit" ){ // While there are more requests, cin >> ammt; balance = inqueries(request, ammt); // call the appropriate // functor from the map. cout << request << " request performed, current balance is now " << balance << endl; cout << endl << ">> " ; cin >> request; } return 0; }
©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.