Macros

The preprocessor allows you to define macros, which are used rather like functions, except that they directly insert text into the source that the compiler sees.

The #define Directive

The #define directive changes the meaning of a word, or token, so that it is replaced by substitute text. This token is called a C macro. In this example, I have created three macros, PI, IF, and THEN:

#define PI 3.1412
#define IF if(
#define THEN )
#define TWOPI 2*PI

After these definitions, you can type IF x > PI THEN, and the preprocessor replaces the macros with their substitute text; this is called macro expansion. The compiler then actually sees if (PI > 3.1312). If the expanded text itself contains a macro, that will be further expanded. The preprocessor does not know about C++ syntax and will even let you redefine keywords; this is a bad idea because you will confuse any normal C++ programmer completely. A common naming convention is to put any macros in uppercase, which makes it clear when they're being used.

Some standard predefined macros are available in any C++ preprocessor; __FILE__ expands to the current file, and __LINE__ to the current line in that file.

Macros can have parameters; there must be no space between the name and the opening parenthesis:

#define SQR(x) x*x

In this example, SQR(2) expands as 2 * 2. Any macros found in the substitution are themselves expanded, so SQR(PI) is expanded to 3.1412 * 3.1412. But SQR(1+t) expands as 1+t * 1+t, which is wrong without parentheses around 1+t so the macro must be defined as follows:

#define SQR(x) (x)*(x)

SQR() now behaves as expected. Unfortunately, however, SQR(f(x)) is replaced by (f(x))*(f(x)), which means the function f(x) is called twice. It's not necessarily wrong (unless the function has side effects), but it could be very inefficient. And SQR(i++) is definitely wrong. I'm showing you these problems so you can appreciate that inline template functions do the job much better than macros, and so you can be thankful that nobody has to do C anymore. This also emphasizes that macros are not functions and in fact they do a fairly simple-minded substitution.

If you are ever in doubt about the result of a macro substitution, then I encourage you to use the cpp utility as described previously. As long as you don't include any system files, the output will be quite short.

Stringizing and Token Pasting

The stringizing operator (#) is found only in macro substitutions, and it basically quotes the parameter that follows it, as in the following example:

;> #define OUT(val) cout << #val << " = " << val << endl
;> int i = 22;
;> OUT(i);
i = 22

;> #define S(name)  #name " is a dog"
;> S(fred);
(char*) "fred is a dog"

Here OUT(i) becomes cout << "i" << " = " << i << endl; the magic with the second macro S() is that adjacent string literals are automatically concatenated by C++ to build up the larger string; that is, S(fred) becomes "fred" " is a dog". C++ does this mostly to support multiline strings but also to let you do this kind of trick.

string a_long_string =
"after several days they found themselves "
"within sight of the breakers around a barren island";

After you are finished with a macro, it is possible to undefine it, by using #undef. The redefinition of a macro is not an error, but it is considered bad manners, and you get irritating warnings when you try to do it. Also, to #undef a macro makes it clear that the macro is used for a particular limited purpose that is now over. For example, the following kind of macro can save a lot of typing in switch statements, especially if there are many cases. It is less error prone than a case statement, where people often leave off the break statement. Notice in the following example that the macro definition and “undefinition” are put as close to the code as possible:

char *message(int id)
{
  char *msg;
   switch(id) {
#define CASE(x)  case x:  msg = #x;  break;
  CASE(NOT_FOUND)
  CASE(UNABLE_TO_REACH)
  CASE(DISMISSED)
#undef CASE
 }
 return msg;
}

You can put all these CASE lines into a header file, called errors.h (that is, everything between the #define and the #undef.) Then you can define an enumeration and an operator to display the header files, like this:

enum Errors {
#define CASE(x)  x ,
#include "symbols.h"
#undef CASE
 END_VAL  // I need this dummy at the end w/out a comma...
};
...
ostream& operator<< (ostream& os, Errors e) {
   char *msg;
  switch(id) {
#define CASE(x)  case x:  msg = #x;  break;
#include "symbols.h"
#undef CASE
   }
 return  os << msg;
}

The token-pasting operator (##) allows you to concatenate tokens. Like the stringizing operator, it applies to parameters. This means that new valid C++ identifiers can be constructed:

#define INIT(name) init_##name

So INIT(unit) will expand as init_unit. Combining this with the predefined macro __LINE__, you can generate a unique name at each source file with INIT(__LINE__), and you will get init_156, init_175, and so on, depending where the macro is used.

You can extend the substituted text over several lines by using the backspace continuation character (). Before templates, C++ would produce generic classes like this:

#define POINT(t) struct Point_##t {  
         T x,y;                     
          T(a,b) : x(a),y(b) { }      
      };

Note that the last line of the substitution does not have a continuation. POINT(float) would expand to this:

struct Point_float {  public: float x,y; float(a,b) : x(a),y(b) { }   };

This technique would work for generating POINT(float), but you can imagine how clumsy this technique is for serious classes.

When Not to Use Macros

There is one use of macros that nobody—not even C programmers—approves of anymore: using macros for symbolic constants. It is much better to say const double PI=3.1412 than #define PI 3.1412. The type of the constant in the first case is completely explicit, but in the second case it is implicit (not everyone knows that floating-point constants are double). Also, in traditional C++ systems, the compiler doesn't know anything about the preprocessor, so it just sees 3.1412 in the second case. Thus, the debugger has no record of PI's existence either, so it cannot be inspected, and you will not be able to browse for the symbol PI either. (UnderC is different from normal compilers like GCC in this respect, but this is because the preprocessor is always available when debugging in interactive mode. That is, if PI was defined as a macro, then typing PI at the ;> prompt would indeed give its value.)

Another problem with macros in C++ has to do with scope; the preprocessor is completely outside the C++ scope system, and so you never know when a macro is going to (silently) damage your program and generally make you wish it had never been born. This is why people insist on writing macros in uppercase (to make them obvious) and always using parameters with their macros; if the macros always have parameters, they will never be confused with constants, which are also traditionally done in uppercase.

When people see the power of macros (especially if they've come from another language and still feel homesick), they often want to make C++ look like their favorite language. You can make C++ look like BASIC or Pascal, but you will not be impressing the person who has to look after your code, who is expecting C++. For example, the following example can be made to compile and run with any C++ compiler:

IF a > b OR n < 5 THEN
    PRINT a;
    FOR I = 1 TO n DO
       PRINT I*b;
    NEXT
ENDIF

These macros are all straightforward, except for the FOR loop. Just for kicks, I'll show you how that one is done:

#define FOR  for(int& _ii_ = (
#define TO   );  _ii_ <=
#define DO  ;  _ii_++) {
#define NEXT }

FOR k = 1 TO n DO expands as follows:

for(int& _ii_= (k = 1) ; _ii_ <= n; _ii_++) {

You define a temporary reference variable _ii_ as an alias for the loop variable i. The International Standards Organization (ISO) C++ standard promises that this temporary reference is valid only within the for loop, so you can use the trick repeatedly. After the reference is bound to the variable, any action (such as ++) on the reference acts on the variable.

NOTE

Although making C++ look like BASIC is entertaining, it is wise not to take it seriously. You must learn the language as it is: For example, || means “or”, && means “and,” and it is better to use { than to use BEGIN. No one will stop you from writing some real BASIC occasionally, but don't mix it up with C++. Programs are public documents, and they must be written in a public language. There is a danger of working in a language that is only readable by one person.


There are cases in which one or two control macros can make the language easier to read and maintain. The following is my favorite:

#define FOR(k,n)   for(k = 0;  k < (n); k++)

Whenever I see FOR() in code, I know that it's just the usual 0 to n-1 loop. When I see a for(;;),I know something different is going on, like a 1 to n loop. for loops are not easy on the eye, and many people misread them (for instance, one of the is in a loop could be replaced with a j). The macro must go as the following two lines:

FOR(k,n):
for(k = 0;  k < (n); k++) xxx.

Here is another of my favorite macros:

#define FORALL(ii,ic)  for(ii = (ic).begin();  ii != (ic).end();  ++ii)

This is a very common control statement when iterating over all elements of a container. Writing things like this is often a matter of taste, but I prefer to type FORALL(ii,ls) ii->do_something(); rather than for_each (ii.begin(),ii.end(),operation) because the latter means I still have to define the operation function.

Generally, you should not try to make macros complicated; the simpler they are, the better. Remember that the debugger can tell you nothing about what's going on inside a macro substitution.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.122.244