Search

Sponsored Links

Meta

Categories

Archives

Recent Posts

RSS Feeds

08
Dec

C++ Advanced Tutorial - Lesson 6

Related Blog Items

Please refer Lesson 5..

6. THE PREPROCESSOR
6.1 #include
6.2 Conditional Compilation
6.3 #define Symbols for Conditional Compilation
6.4 #define for Constants vs. C++ Constants
6.5 #define for Macros vs. C++ Inline Template Functions
6.6 Line Numbers
6.7 Predefined Symbolic Constants

6. THE PREPROCESSOR

6.1 #include

The #include preprocessor directive causes a copy of a specific file to be pasted in place of the directive before compilation. It is used when another file contains the type definition that you want to use. For global variables and functions contained in another file, you do not include that file. Instead you inform the compiler with the following line before you use it that it is a global variable or function and the compiler should find it out itself:

extern ULONG m_refCount;       // global variable
extern void Lock(BOOL fLock);  // global function

There are two forms of the #include directive:

#include <filename>
#include "filename"

File included in “< >” will be searched in the system’s pre-defined locations, while the file included by quotes will be first searched in the current directory, then the pre-defined locations. “< >” is used for standard library files.

Most included files are *.h header files. But other files such as *.cpp source files can also be included, as long as the definition you want to invoke.
If header file A makes use of a type defined in file B, normally it should include B before using that type. Such a file can be called “self-contained”. Not all files are “self-contained”. Some library header files are very comprehensive and contains lots of type definitions. Different definitions may use other definitions defined in other files. If we want such files to be self contained, we may virtually end up with each header file including all other header files. Considering that most clients include such a header file just to use one or two definitions in it, it will be a big waste of space. Therefore, such a header may decide not to include some other files. If you want to include use type A defined in file X, and type A uses type B which is defined in file Y, it is your duty to insure that file Y is included in front of file X:

//header1.h
#define _STRUCT_A
struct A 
{
  int a;
};
 
//header2.h
#define _STRUCT_B
struct B 
{
  int b;
};
 
//header3.h
#if defined _ STRUCT_A
struct C 
{
  int c;
  A a;
};
#endif
 
#if defined _ STRUCT_B
struct D 
{
  int d;
  B b;
};
#endif
 
//main
// header1 must proceed header 3, while header 2 is not necessary
#include "header1.h"  
#include "header3.h"
 
void main(int argc, char* argv[])
{
  C c;
}

6.2 Conditional Compilation

#if expression

#endif

Two special expression are:

#if !defined (SYMBOL)
#if defined (SYMBOL)

Their simplified forms are:

#ifdef
#ifndef

Conditional compilation is commonly used as a debugging aid. You can first use

#define DEBUG 1

then enclose all debugging codes in

#if DEBUG
#endif

Then after debugging is finished you only need to change “1″ to “0″ to disable all debugging codes.

6.3 #define Symbols for Conditional Compilation

The #define preprocessor directive creates a file-scope symbolic constant. When preprocessor sees

#define

it will create a constant named “CONSTANT” in the scope of this file. Then “defined CONSTANT” will return true. Therefore, you can use the following predirectives to wrap some code:

// "#ifndef _ANY_SYMBOL" is a simplified version
#if !defined _ANY_SYMBOL 
#define _ANY_SYMBOL
...
#endif

The first time preprocessor sees a #include and opens this file, _ANY_SYMBOL is not defined, so the preprocessor defins _ANY_SYMBOL and goes on. Next time when preprocessor sees another include of this file and encounters the #if, it will skip the whole block. This is how you can avoid the enclosed code from being included for more than once, which may cause serious problems.

The constant can be anything. In convention we use the capitialized file name. For example, if the header file is “Employee.h”, the constant may be “EMPLOYEE_H”, “_EMPLOYEE”, “_EMPLOYEE_H”, etc. Sometimes a globally unique ID may be used so that there won’t be any confusion.
A file-scope constant applies only to the code after this. To undefine it, say

#undef
A preprocessor constant can also be defined in a global scope. In Visual C++, the constants you put in the “Project | Settings | C/C++ | Preprocessor directives” field will be defined in global scope. Some commonly used constants like “WIN32″ and “_DEBUG” are defined in that field. When you choose “Win32 Debug” in “Project | Settings”, the “_DEBUG” constant will be added automatically. If you choose “Win32 Release”, constant “NDEBUG” will be added. Then lots of Win32 macros such as assert and your own code may check this constants and decide what to behave. For example, if “NDEBUG” is defined, the code within assert will be skipped to increase performance:

void assert(...)
{
#if defined NDEBUG
    // assertion work to be done
#enif
}

Look at the following example. Suppose you have two classes defined in two different header files

class A  
{
public:
  A()
  {
#if defined _SAYHELLO
    printf("Hellow from class A!\n");
#else
    printf("Hi from class A!\n");
#endif
  }
};
 
class B  
{
public:
  B()
  {
#if defined _SAYHELLO
    printf("Hellow from class B!\n");
#else
    printf("Hi from class B!\n");
#endif
  }
};

If you put “#define _SAYHELLO” in one of the header files, only one constructor will say “Hello” and the other will say “Hi”. If you put “_SAYHELLO” into the project setting, both constructors will say “Hello”.

6.4 #define for Constants vs. C++ Constants

#define MAX_POWER  15.3
#define OVERFLOW_MSG   "Data overflow!"

#define constants such “MAX_POWER” can not be seen by compiler. It is C-style. C++ style constants would be using constants. Specifically, if you want to restrain the constant within the scope of a class, use a constant static class member:

private:
      const static int I;

In the implementation file, you have to initialize it as

const int A::I = 3;

However, if you want to use I as array size, compiler has to know its value, and the above way does not work. You can use a “enum hack”:

private:
      enum {I = 5};
      float a[I];

6.5 #define for Macros vs. C++ Inline Template Functions

Macros are C-style. It has two advantages: it can be applied to different types and it saves a method call. Its disadvantage is that there is no type checking. In C++ it can be replaced by template and inline methods which has all the benefits of macros and meanwhile performs type checking.

#define area(r) (3.1416 * (r) * (r))

“( )” around the argument r and the entire expression forces the proper order of evaluation when the argument is an expression.
Then when preprocessor sees a line like

area = 3.3 - area(c + 2);

it will just replace it with

area = 3.3 - (3.1416 * (c + 2) * (c + 2));

Such a C-style macro can be replaced by a C++ inline template function with all macro benefits but without macro drawback:

template <class T>
inline const T & Area(const T & r)
{
    return 3.1416 * r * r;
}

If the replacement text in a macro is longer than a line, backslash “\” must be put at the end of the line to indicate that the replacement text continues in the next line:

#define check(msg) if(FAILED(hr)) \
    {\
        ::MessageBox(NULL, msg, "Server", MB_OK | MB_TOPMOST); \
        return; \
    }\
=>#define macros with stringizing operator #

In the macro body, if you put # operator in front of the parameter, the preprocessor will wrap the parameter with double quotes. So if a macro defines as

#define m(msg) ::MessageBox(NULL, msg, "Server", MB_OK | MB_TOPMOST);

if you say it will become

m(buf);

It will become

::MessageBox(NULL, buf, "Server", MB_OK | MB_TOPMOST);

If the macro is

#define m(msg) ::MessageBox(NULL, #msg, "Server", MB_OK | MB_TOPMOST);

if will become

::MessageBox(NULL, "buf", "Server", MB_OK | MB_TOPMOST);
=>#define macros with token pasting operator ##

## is used to join two tokens together. So if the macro is defined as

#define m(x) parameter##x

then if you say

cout <<m(12);

it becomes

cout << parameter12;

6.6 Line Numbers

#line 100
#line 100 "file1.c"

The first line causes the following line numbers to start from 100, and all compiler messages is written into “file1.c”. It is used to make the messages produced by syntax errors and compiler warnings more meaningful.

6.7 Predefined Symbolic Constants

They start and end with two underscores:

__LINE__     //line number of current source line - an integer
__FILE__     //presumed name of the source file - a string
__DATE__     //the date the source file is compiled - 
             //a string such as "Jan 19 1999"
__TIME__     //the time the source file is compiled - 
             //a string such as "15:25:35"
__STDC__     //integer constant 1. This is intend to indicate 
             //that the implementation is ANSI compliant

Popularity: 18%

You need to log on to convert this article into PDF


Related Blog Items

No Comments

No comments yet.

Leave a comment

*
To prove you're a person (not a spam script), type the security word shown in the picture.
Anti-spam image