The Best of the CSS 342 BlogThis page includes posts from previous years' CSS 342 blogs. It's meant to be a more permanent location for some of our course's more helpful hints.
The preprocessor is a feature of C++ compilers inherited from C and, as with many similar features, there are useful aspects of it and aspects that are to be avoided in C++, as they have been superseded by better language features. As implied by the name, the preprocessor is a program that processes your source files before the compiler sees them. In other words, it alters the code itself.
First, the useful features. The most useful feature is the ability to define symbols and test for their existence, again, before the compiler sees the code. This allows for conditional inclusion of code: conditional compilation. You've already been exposed to the two major uses of this: the ability to prevent a .h file being included twice and the ability to select different sections of code based on target machine and OS. For example:
#ifndef _FILENAME_H_ #define _FILENAME_H_ . . . #endifThis will cause the body of the .h file to be skipped if the preprocessor symbol _FILENAME_H_ is already defined (presumably, by the file having been already included). The other "legitimate" use of this technique looks like:
#ifdef UNIX // Unix-specific code here #else // Other OS-specific code #endifIn this case, the code is selected based on the symbol or symbols defined. Typically, they are defined on the command line for g++, with the -D option (for example, -DUNIX; note the lack of a space). Visual Studio has a preference that allows you to specify preprocessor symbols. Additionally, most compiler predefine certain symbols; check the compiler's documentation (or search online) to find out what they are.
Bad, bad codeThe thing to avoid (and that is unnecessary in C++) is the macro. A macro is like an inline function that places no restriction on the type of its arguments. As such, you can accomplish the same thing using templates and inline. However, for completeness' sake, consider the following common example:
#define MAX(a,b) (a) > (b) ? (a) : (b)The first thing you notice is that the "variables" are enclosed in parentheses. This is to ensure that whatever they get replaced by gets evaluated before the '>', '?', and ':' of the macro. The capitalization of the macro name is merely a convention. To see the effect of this macro, consider the following code snippet:
int x, y, z; double p, q, r; z = MAX(x, y); r = MAX(p, q); z = MAX(x++, y++);Remember, the substitution is done before the compiler sees the code, so the result, after substituting for 'a' and 'b' is:
int x, y, z; double p, q, r; z = (x) > (y) ? (x) : (y); r = (p) > (q) ? (p) : (q); z = (x++) > (y++) ? (x++) : (y++);Clearly, the first two uses of the macro are fine, but the third has problems. This arises because the preprocessor doesn't take into account the programming language; it just does a simple text substitution, like a text editor might. So, in this case, since 'a' and 'b' appear twice in the macro, the substituted text appears twice in the final code, and either x or y (whichever is greater) gets incremented twice. Side effects can become even more complex if a function call, for example, is substituted for a macro variable.
Take home message: Don't do it. Preprocessor macros are evil and unnecessary in C++.