Previous section   Next section

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


18.3. Aliases

The typedef keyword (C++-98: 7.1.3) has precisely the behavior we are looking for when it comes to contextual type definitions, in that it merely defines another name, relative to a given context, for a particular type. In effect it is an alias for the existing type. For example, the types String::iterator and char * are the same, and may be used interchangeably, which is a good thing.



void make_upper(char *begin, char *end);





void set_chars(String &s)


{


  make_upper(s.begin(), s.end());


}



However, conceptual type definitions are all about defining new types based on existing ones. The aliasing that is the very instrument of contextual type definitions causes conceptual type definitions to be, at best, a very weak mechanism for type distinction.

18.3.1 Erroneous Conceptual Type Interchange

If we look again at our Socket class, we can easily envisage code such as the following:



AddressFamily family   = AF_INET;


SocketType    type     = SOCK_DGRAM;


Protocol      protocol = IPPROTO_UDP;





Socket(type, family, protocol);



Ouch! type and family were reversed by an overworked programmer. Fortunately the first test of the system will reveal that there is a problem, since the socket will not be opened due to the invalid family and type requested.

Actually, that may not be true. There's an altogether nastier facet to this problem. On one of my systems AF_INET and SOCK_DGRAM actually have the same value: 2! So now we've got the appalling situation that this code will work as expected because we have a bug that is benign in a platform-dependent manner. It's not hard to envisage porting the application to another platform, on which AF_INET and SOCK_DGRAM have different values, and spending days in the (correctly) ported platform-dependent parts because the system no longer works.

In certain cases, it is possible for such invalid exchanges of instances of conceptual type definitions to fail to cause breaking changes but merely to hamper system effectiveness. Such errors can be extremely hard to find. In one real case I came across, a logging API was defined with functions similar to the following:



typedef int      TE_DbgLevel;


typedef uint32_t TE_Flags;





void TraceEntry(TE_DbgLevel level, TE_Flags flags, . . .);



There were many cases throughout the code base where the flags and the level had been mistakenly reversed. (The API had formerly had them the other way round, and an "improvement" by one developer had swapped them to the current definition. This breaks one of the cardinal maintainability rules, which is to avoid changing function definitions, and instead favor defining new functions and "retiring" the old ones.) By coincidence, the debugging levels were between 0 and 7, and the only commonly used trace flags were those three whose values were 0x1, 0x2, and 0x4. The effect on the system was that performance was compromised to a significant degree.

Imperfection: C++ provides no type safety for the use of conceptual defined types, except where such types have incompatible underlying types.


Once I had come across one of these reversals, I applied the technique we're going to talk about in section 18.4, and found over a hundred more. System performance was improved by over 50% once they'd all been corrected!

18.3.2 No Overloading on Conceptual type

There's another problem caused by (typedef) aliasing. Imagine we had a Network class that acted as a supervisor for socket-based communications within a process. Such a class might provide functions to shutdown all sockets based on particular criteria, as in:



class Network


{


public:


  void QuenchMatchingSockets(AddressFamily family);


  void QuenchMatchingSockets(SocketType type);


  void QuenchMatchingSockets(Protocol protocol);





  . . .



But our compiler will reject this class, since it rightly cannot provide three overloads with the same fundamental signature, which in this case is void QuenchMatching Sockets(int);. Even worse is where we're writing such overloaded methods using conceptual types whose fundamental types are different for a particular platform. When ported to another platform two of more of the underlying types may now be equivalent, and the code will no longer build.

Imperfection: C++ does not support overloading on conceptually defined types, except where such types happen to have different underlying types on a particular platform.



      Previous section   Next section