Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 3.  Resource Encapsulation


3.3. Wrapper Proxies

A small, but often useful, step up from POD types is wrapper proxies. Such types do nothing to address the automatic acquisition or release of resources; they merely make it easier to use the types that they wrap.

Consider that you're working with a source-parser plug-in product, whereby you register a dynamic library (see Chapter 9) that receives notifications from a parsing engine in response to certain events. The entry point might look like Listing 3.1:[1]

[1] The callback API will most likely be in pure C, for the reasons we discuss in Part Two.

Listing 3.1.


enum SPEvent { . . . };





struct ParseContext


{


// Callback functions


  void *(*pfnAlloc)(size_t cb);


  void (*pfnFree)(void *p);


  void *(*pfnRealloc)(void *p, size_t cb);


  int (*pfnLookupSymbol)( char const *name, char *dest


                        , size_t *pcchDest);


// Data members


  . . .


  char const *currTokBegin;


  char const *currTokEnd;


  . . .


};





int SrcParse_LibEntry(SPEvent event, ParseContext *context);



Clearly, dealing with the ParseContext structure is going to be messy, as in Listing 3.2.

Listing 3.2.


// MyPlugIn.cpp





int SrcParse_LibEntry(SPEvent event, ParseContext *context)


{


  switch(event)


  {


    case SPE_PARSE_SYMBOL:


      MyPlugIn_ParseSymbol(context);


  . . .


}





void MyPlugIn_ParseSymbol(ParseContext *context)


{


  size_t  tokLength;


  char    *tokName;


  size_t  cchDest;


  tokLength = 1 + (context->currTokEnd –context->currTokBegin);


  tokName = strncpy( (char*)(*context->pfnAlloc)(sizeof(char)


                 * tokLength), context->currTokBegin, tokLength);


  size_t  cchDest   = 0;





  int  cch = (*context->pfnLookupSymbol)(tokenName, NULL, &cchDest);


  . . .


}



Not pretty is it? As well as being a royal pain to deal with all that code, we're messing around with raw pointers in our logic code, and the handling of errors and exceptions is sketchy at best. All these things are what C++ positively excels at, so surely we can improve it.

The answer is that most of the complexity can be easily encapsulated within a wrapper proxy

Listing 3.3.


class ParseContextWrapper


{


public:


  ParseContextWrapper(ParseContext *context)


    : m_context(context)


  {}





  void *Alloc(size_t cb)


  {


    return (*m_context->pfnAlloc)(cb);


  }





  string CurrentToken()


  {


    return string(m_context->currTokBegin, m_context->currTokEnd);


  }





  string LookupSymbol(string const &token)


  {


     . . .





  int OnParseSymbol();





private:


  ParseContext *m_context;


};



which leads to considerably simpler user code.

Listing 3.4.


int SrcParse_LibEntry(SPEvent event, ParseContext *context)


{


  try


  {


    ParseContextWrapper      cw(context);





    switch(event)


    {


      case SPE_PARSE_SYMBOL:


        return OnParseSymbol();





      . . .


  }


  catch(ParseException &x)


  {


    . . .



APIs such as this represent the stereotypical wrapper proxy case. ParseContextWrapper does not in any way own the ParseContext pointer it is given, it merely enables client code to work with a simpler, and presumably well-tested, interface over the base API. Note that I've made the m_context member private out of habit; you could arguably leave it public, since the client code has access to the raw ParseContext anyway, but old habits die hard.[2]

[2] Be thankful I didn't go really paranoid and take a pointer reference in the constructor, and then set the raw pointer to NULL, so nothing else could use it.


      Previous section   Next section