Solution

I l @ ve RuBoard

graphics/bulb.gif

C++ features often, but not always, cancel out the need for using #define . For example, " const int c = 42 ;" is superior to " #define c 42 " because it provides type safety, and avoids accidental preprocessor edits, among other reasons.

There are, however, still a few good reasons to write #define .

1. Header Guards

This is the usual trick to prevent multiple header inclusions.

 #ifndef MYPROG_X_H #define MYPROG_X_H // ... the rest of the header file x.h goes here... #endif 

2. Accessing Preprocessor Features

It's often nice to insert things such as line numbers and build times in diagnostic code. An easy way to do this is to use predefined standard macros such as __FILE__ , __LINE__ , __DATE__ , and __TIME__ . For the same and other reasons, it's often useful to use the stringizing and token-pasting preprocessor operators (# and ##).

3. Selecting Code at Compile Time (or Build-Specific Code)

This is an important, if overused , category of uses for the preprocessor. Although I am anything but a fan of preprocessor magic, there are things you just can't do as well, or at all, in any other way.

a) Debug Code

Sometimes, you want to build your system with certain "extra" pieces of code (typically, debugging information) and sometimes you don't.

 void f() { #ifdef MY_DEBUG   cerr << "some trace logging" << endl; #endif   // ... the rest of f() goes here... } 

The thing about this code, though, is that what we really have is two different programs. When we compile it with MY_DEBUG defined, we get source code that includes output to cerr , and the compiler will check that line's syntax and semantics, too. When we compile it without MY_DEBUG defined, we get a different program that does not include the output to cerr , and the compiler cannot verify the correctness of the excluded code.

Usually it is better to use a conditional expression instead of the #define .

 void f() {   if( MY_DEBUG )   {     cerr << "some trace logging" << endl;   }   // ... the rest of f() goes here... } 

This way, there's only one program to compile and test, the compiler can validate all the code, and the compiler can easily eliminate the unreachable code if MY_DEBUG isn't true.

b) Platform-Specific Code

Usually, it's best to deal with platform-specific code using a factory pattern, because this approach makes for better code organization and runtime flexibility. Sometimes, however, there are just too few differences to justify a factory, and the preprocessor can be a useful way to switch optional code.

c) Variant Data Representations

A common example is that a module may define a list of error codes, which outside users should see as a simple enum with comments, but which inside the module should be stored in a map for easy lookup. That is:

 // For outsiders // enum Error {   ERR_OK = 0,            // No error   ERR_INVALID_PARAM = 1, // <description>   ... }; // For the module's internal use // map<Error,const char*> lookup; lookup.insert( make_pair( ERR_OK,                           (const char*)"No error" ) ); lookup.insert( make_pair( ERR_INVALID_PARAM,                           (const char*)"<description>" ) ); ... 

We'd like to have both representations, without defining the actual information (code/message pairs) twice. With macro magic, we can simply write a list of errors as follows , creating the appropriate structure at compile time:

 ERR_ENTRY( ERR_OK,            0, "No error" ), ERR_ENTRY( ERR_INVALID_PARAM, 1, "<description>" ), ... 

The implementations of ERR_ENTRY and related macros is left to the reader.

These are three common examples, but there are many more. Suffice it to say that although the C preprocessor should be avoided in many places, it still has its share of useful features that can, when used judiciously, make writing C++ code easier and safer.

Guideline

graphics/guideline.gif

Avoid preprocessor macros:

  • Except #include guards

  • Except conditional compilation for portability or debugging in . cpp files (not in . h files!)

  • Except #pragmas used to disable known-to-be- innocuous warnings, but such #pragmas must always be inside a "conditional compilation for portability" guard to prevent warnings from compilers that do not understand them


To see what the inventor of the C++ language thinks of the preprocessor, check out section 18 of [Stroustrup94].

I l @ ve RuBoard


More Exceptional C++
More Exceptional C++: 40 New Engineering Puzzles, Programming Problems, and Solutions
ISBN: 020170434X
EAN: 2147483647
Year: 2001
Pages: 118
Authors: Herb Sutter

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net