Professional C__ - Marc Gregoire [154]
if (debug) {
va_start(ap, str);
vfprintf(stderr, str, ap);
va_end(ap);
}
}
Code snippet from VarArgs\VarArgs.cpp
First, note that the prototype for debugOut() contains one typed and named parameter str, followed by ... (ellipses). They stand for any number and types of arguments. In order to access these arguments, you must use macros defined in You can use the function in the following way: debug = true; debugOut("int %d\n", 5); debugOut("String %s and int %d\n", "hello", 5); debugOut("Many ints: %d, %d, %d, %d, %d\n", 1, 2, 3, 4, 5); Code snippet from VarArgs\VarArgs.cpp Accessing the Arguments If you want to access the actual arguments yourself, you can use va_arg() to do so. Unfortunately, there is no way to know what the end of the argument list is unless you provide an explicit way of doing so. For example, you can make the first parameter a count of the number of parameters. Or, in the case where you have a set of pointers, you may require the last pointer to be nullptr. There are many ways, but they are all burdensome to the programmer. The following example demonstrates the technique where the caller specifies in the first named parameter how many arguments are provided. The function accepts any number of ints and prints them out. void printInts(int num, ...) { int temp; va_list ap; va_start(ap, num); for (int i = 0; i < num; i++) { temp = va_arg(ap, int); cout << temp << " "; } va_end(ap); cout << endl; } Code snippet from VarArgs\VarArgs.cpp You can call printInts() as follows. Note that the first parameter specifies how many integers will follow. printInts(5, 5, 4, 3, 2, 1); Code snippet from VarArgs\VarArgs.cpp Why You Shouldn’t Use C-Style Variable-Length Argument Lists Accessing C-style variable-length argument lists is not very safe. As you can see from the printInts() function, there are several risks: You don’t know the number of parameters. In the case of printInts(), you must trust the caller to pass the right number of arguments as the first argument. In the case of debugOut(), you must trust the caller to pass the same number of arguments after the character array as there are formatting codes in the character array. You don’t know the types of the arguments. va_arg() takes a type, which it uses to interpret the value in its current spot. However, you can tell va_arg() to interpret the value as any type. There is no way for it to verify the correct type. Avoid using C-style variable-length argument lists. It is preferable to pass in an array or vector of values, or to use initializer lists, which are described earlier in this chapter, or to switch to the type-safe C++11 variable-length argument lists using variadic templates, described in Chapter 20. Preprocessor Macros You can use the C++ preprocessor to write macros, which are like little functions. Here is an example: #define SQUARE(x) ((x) * (x)) // No semicolon after the macro definition! int main() { cout << SQUARE(5) << endl; return 0; } Code snippet from Macros\Square.cpp Macros are a remnant from C that are quite similar to inline functions, except that they are not type checked, and the preprocessor dumbly replaces any calls to them with their expansions. The preprocessor does not apply true function-call semantics. This behavior can cause unexpected results. For example, consider what would happen if you called the SQUARE macro with 2 + 3 instead of 5, like this: