The code is taken from a real application, which is described very briefly here so you can get an idea of what the code is trying to accomplish.
The application does a random number sampling experiment in order to test some hypotheses about what strategies people use to estimate time intervals. The experimenters who use the program type in the name of the strategy they want to test, and at one point the program has to call a different function depending on the name of the strategy the experimenter typed in.
sample.h
.
There are two sampling strategies, and the header file begins by
setting up symbolic names for numerical constants that can be used
to identify these strategies, plus another constant that should "never"
be used:
As the '#define UNDEFINED_STRATEGY 0 #define RANDOM_R_STRATEGY 1 #define RANDOM_NR_STRATEGY 2
#
' at the beginning of each line indicates, these
lines are handled by the preprocessor. Wherever one of the names listed
here appears in a source code file, the preprocessor will substitute the
text that appears on the remainder of the line. That is, wherever the
program uses the symbol RANDOM_R_STRATEGY
, the compiler will
see the character 1
because of preprocessor text substitution.
In C++ it would have been better to use integer constants rather than these preprocessor definitions because then the compiler would be able to check that the symbols are being used properly as integers. But I didn't. In fact, these symbolic constants were never actually used for anything in this program, but I have left them in this Web page just to give some idea of how they might be used.The program needs to put pointers to functions into a data structure. The functions all take two arguments of types int and bool and return an int, so here is the definition of a new data type for such functions, and the definition of a structured data type that includes a pointer to such a function:
Now the header file can provide the equivalent of function prototypes for some functions of type chooseDist_t:typedef int chooseDist_t(int, bool); struct strategy { char *name; int type; chooseDist_t *chooseDist; };
There will be a list of strategyextern chooseDist_t noStrategy, selRandom_r, selRandom_nr;
struct
s, and as
we shall see, there will be numStrategies
elements in this
array:
extern const strategy strategyList[]; extern const int numStrategies;
#include
s the header
file we just defined. In addition, this file is the one that actually
initializes the global data declared in the header file:
#include "sample.h" const strategy strategyList[] = { // - name ---- - type ---------- - chooseDist - {"undefined", UNDEFINED_STRATEGY, noStrategy }, {"random_wr", RANDOM_R_STRATEGY, selRandom_r }, {"random_wor", RANDOM_NR_STRATEGY, selRandom_nr }, }; const int numStrategies = sizeof(strategyList) / sizeof(strategy);
The comment line is to help you remember the names of the three fields of the data structures being initialized.There are several things to notice about these two statements. First, the number of elements in
strategyList[]
is
determined by the number of initializers coded. This version of the
program initializes
just three elements, and the compiler will give us this value in the
constant
numStrategies
using the sizeof operator (which is
part of the C and C++ languages, not a function).It so happens that the three preprocessor constants have the same numerical values as the subscripts of their array elements, but this is not a requirement in order for this code to work properly. The three initialization lines could appear in any order, and the type codes would be properly assoicated with the corresponding names and function pointers..
The point of all this is that the array can be redefined easily when the experimenters decide they want to add new strategies to the program.
Read the man page for strcasecmp() to see how that function works.int i; bool found = false; char strategy[80]; ifstream configFile("config.dat", ios::in); // Be sure config.dat opened all right. if (!configFile) { cerr << "Unable to open \"config.dat\"" << endl; exit(EXIT_FAILURE); } // Read strategy name and locate it in strategyList configFile >> strategy; for (i = 0; i < numStrategies; i++) { if (!strcasecmp(strategy, strategyList[i].name)) { found = true; strategyType = strategyList[i].type; break; } } if (!found) { cerr << "\"" << strategy << "\" is not a valid strategy name" << endl; exit(EXIT_FAILURE); }
When the above piece of code finishes executing,
a global integer named strategyType will have a value of
0, 1, or 2, corresponding to the names UNDEFINED_STRATEGY
,
RANDOM_R_STRATEGY
, or RANDOM_NR_STRATEGY
.
This is the code that demonstrates calling a function using a pointer. Remember, chooseDist is the name of a member of the strategy data structure that holds a pointer to a function of type chooseDist_t.// Select first reference distribution refDist = strategyList[strategyType].chooseDist(target, true); // Get a matching sample while (!match(refDist, target, grand(0))) { refDist = strategyList[strategyType].chooseDist(target, false); }
Exercise: Figure out how to improve the program so that we could guarantee that strategyType has the proper subscript value without counting on the programmer to set the predefined constantsUNDEFINED_STRATEGY
,RANDOM_R_STRATEGY
, andRANDOM_NR_STRATEGY
to the values 0, 1, and 2 respectively.
#include "sample.h" int selRandom_r(int target, bool first) { return rand() % numDists; }