The Artima Developer Community
The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next
Sponsored Link

The C++ Source
Reducing Preprocessor Namespace Pollution
by Bjorn Karlsson and Matthew Wilson
November 6, 2004

Page 1 of 3  >>

Advertisement

Summary
Trampling roughshod over the global namespace (and all other namespaces) with the macro preprocessor is one of the least friendly things you can do in C++. Why, then, is it the case that many popular APIs do exactly that? The authors look at this nasty problem, and demonstrate a simple technique that can be used to obviate it, and be good C++itizens.
The Win32 API
Is like a sharp poke in the eye.
Its macros obtrusive,
Definitions abusive,
And coupling? A grown man could cry!

Sounds like the solution is going to be very important and you should pay close attention? Well, before we ascertain the truth of that, it'd probably serve us all to look at the problem itself.

Solipsism or Selfishness

The problem is quite simple: when selecting between alternate definitions of functions, or types, by discriminating the presence or value of preprocessor symbols, the derived symbols are defined as macros, which pervades the entire compilation unit subsequent to the point of definition. Yikes! That sounds horrid, to be sure, but what exactly does it mean? Naturally, we'll illustrate with a code example.

Consider the following header file, AcmeThreadingStuff.h:

 /* AcmeThreadingStuff.h */
 
 ACMELIB_EXTERNC void TheFuncST(void);  /* This does
 single-threaded stuff */
 #ifdef ACMELIB_MULTI_THREADING_SUPPORTED
 ACMELIB_EXTERNC void TheFuncMT(void);  /* This does
 multi-threaded stuff */
 #endif /* ACMELIB_MULTI_THREADING_SUPPORTED */
 
 ...

This all looks okay so far. Ignoring the meaning of ACMELIB_EXTERNC for the moment, it's clear that there's a single-threaded version of TheFunc, accessible in all builds, and a multi-threaded version that's accessible when AcmeLib determines that multi-threading constructs are supported by the target environment. Let's look further into the file:

 /* AcmeThreadingStuff.h (continued) */
 
 ...
 
 #ifdef ACMELIB_MULTI_THREADING
 # define TheFunc      TheFuncMT
 #else /* ? ACMELIB_MULTI_THREADING */
 # define TheFunc      TheFuncST
 #endif /* ACMELIB_MULTI_THREADING */
These five lines are provided as a convenience to the user, and select the appropriate version of TheFunc based on whether the current build settings are specifying a single-threaded, or a multi- threaded, compilation. (Note: For brevity we're assuming that the symbol ACMELIB_MULTI_THREADING cannot be defined in the absence of ACMELIB_MULTI_THREADING_SUPPORTED. Real-world header files would have a #error in there somewhere to enforce this assumption.)

Now the user of this library can write code without having to worry overtly about the threading model:

 #include "AcmeThreadingStuff.h"
 
 int main()
 {
   TheFunc();
 
   return 0;
 }

(Note: This is the real world, so please don't assume from this simple example that writing correct multi-threaded code is just about calling the right function. That's anything but the case, but we're talking about macros in this installment, so let's leave it at that for now, wink, wink.)

You may have noticed that we have not yet said whether the above code is compiled as C or as C++. As you're likely well aware, operating system and third-party libraries are generally packaged to provide C-APIs; for an exhaustive (exhausting?) discussion as to why, consult chapters 7 & 8 of Imperfect C++ [1]. One important reason is that C is the lingua franca of inter-language communication, since C modules can be directly linked to other languages including C++, D, Delphi, Visual Basic "Classic", assembler, Heron, and many others. C++ APIs can only be interfaced to C++ client code, and there are some serious restrictions even there [1]. Because C++ compilers mangle the names of functions in order to facilitate overloading, C functions visible in C++ compilation units must have the linkage specification extern "C" applied to them. Hence, ACMELIB_EXTERNC is defined to be extern "C" in C++ compilation, and as extern (or as nothing at all) in C compilation units.

So, back to the code: what's the problem? Well, TheFunc is a macro, which means it is seen, and put into effect, at all points in the compilation subsequent to its definition. Consider what happens when the program is enhanced to use some code from a C++ library from another vendor, BaRBSoft. BaRBSoft defines the interface to its library in BaRBSoftStuff.h, and provides the implementation in a static library:

 /* BaRBSoftStuff.h */
 
 #include <string>
 
 namespace BaRBSoft
 {
 
   int TheFunc(char const *regId, int *regIndex);
 
 } // namespace BaRBSoft

We might change our main() function as follows:

 #include "BaRBSoftStuff.h"
 #include "AcmeThreadingStuff.h"
 
 int main()
 {
   TheFunc();
 
   int regIndex;
 
   BaRBSoft::TheFunc("Billy Kriesel", &regIndex);
 
   return 0;
 }
Looks okay, does it not? Alas, this will not compile. The compiler will tell you that the namespace BaRBSoft does not contain a function called TheFuncST (or TheFuncMT, if you're building for multithreaded, i.e. if the symbol ACMELIB_MULTI_THREADING is defined). What gives?

Page 1 of 3  >>

The C++ Source | C++ Community News | Discuss | Print | Email | First Page | Previous | Next

Sponsored Links



Google
  Web Artima.com   
Copyright © 1996-2014 Artima, Inc. All Rights Reserved. - Privacy Policy - Terms of Use - Advertise with Us