Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 18.  Typedefs


18.1. Pointer Typedefs

Can you spot the error in the following code?



class X


{


public:


  X(const PBYTE );


};





X::X(const BYTE *)


{}



You've probably just said that the constructor declaration and definition have different signatures. Well, you may be right, and you may be wrong. The declaration uses a pointer typedef, PBYTE, whereas the definition uses the plain type (albeit another typedef, no doubt) BYTE.

If PBYTE is a typedef, defined (presumably) as



typedef BYTE *PBYTE;



then you're right, and the above class definition is wrong. However, if it is defined as



#define PBYTE  BYTE*



then you're wrong, and (post-preprocessing) the constructor declaration and definitions are the same.

I'm sure some of you are saying that no one would ever do such a thing. Bless you! You either work in a place where all is harmony and professionalism, or you live in an institution. In the real world this is all too common.

The reason pointer typedefs are used is twofold. Firstly, there are, or have been at least, architectures where there were different "natures" to pointers, specifically the nasty far, near, stuff from 16-bit Windows and the mixed-mode Intel architecture. The other reason is that dealing with multilevel pointer types is very verbose, for example:



const int * const * *      p1;


int const * * const *      p2;


int const * const * *      p3;


const int * * const *      p4;


int * const * const *      p5;


int * const * * const      p6;





const int * * const * far  p7;


int * far const * const *  p8;



Is that nasty or what? Some would argue that it is easier to see what's going on in pointer typedef form:



PPCPCInt p1;


PCPPCInt p2;


PPCPCint p3;


PCPPCInt p4;


PCPCPInt p5;


CPPCPInt p6;





FPCPPCInt p7;


PCPFCPInt p8;



I concede that the reduction in eye-traffic is borderline, though it is perhaps a bit more useful when Win16's far-near stuff was involved. What is advantageous about such a scheme is that it normalizes naming for pointers, especially when given the flexibility of qualifier placement.



int const * * const *p9;


const int * * const *p10;


PPCInt const        *p11;


const PPCInt        *p12;



These are actually all the same: PCPPCInt. For good or ill these things exist, and therefore we are exposed to the conflicts described earlier. The answer is twofold: you should choose one or the other convention in your own work, and you should remember this issue whenever you see pointer typedefs.

When working with new libraries, aimed at a modern subset of compilers and operating systems, I tend to favor the plain pointer form, because this is the normal way of doing things, and therefore is easier to read and learn to new users and less likely to offend conventionalists. The Synesis libraries take the other approach, because they've been around a long time (and so had to coexist with all manner of funky memory-model cruft from the past), and have had to work with a much broader range of compilers and target environments.

If you choose not to use pointer typedefs, then you need to add an extra level of precision in the placement of qualifiers. I go with Dan Saks [Saks1996, Saks1999], and always (well, when pointers are involved anyway) place the qualifier after the type, as in:



int const volatile *x;



rather than



volatile const int *x;


const int volatile *x;


const volatile int *x;


volatile int const *x;



Apart from being consistent, you can read backward from the variable to the beginning of the type definition and, no matter how convoluted the definition, get an unambiguously correct understanding of what type it is. If you use the second way, you have to do partial shuffling of the declaration in your head, which seems like far too much work to me, especially when many levels of indirection are involved.


      Previous section   Next section