Previous section   Next section

Imperfect C++ Practical Solutions for Real-Life Programming
By Matthew Wilson
Table of Contents
Chapter 22.  Bolt-ins


22.4. Leveraging Scope

An interesting side effect of the template resolution mechanism is that the symbols that are resolved within a bolt-in template needn't necessarily be part of the template or of its parameterizing class(es). This can be a flaw (and an obscure source of head-scratching consternation), but on occasion may be used to provide a more flexible template.

The Synesis libraries contains a suite of cooperating templates that bolt in reference counting logic to classes that may already be defined in terms of a reference counting interface, as well as those that may not.[3] The templates are based around a set of bolt-ins—SynesisStd::RefCounter and its several skinning (see section 22.2) peers—which take the following form:

[3] These classes have not changed substantially since the mid 1990s, and are therefore looking a bit decrepit. Please bear that in mind; I'd almost certainly design them differently now, but I'm using them here as they illustrate the point.



template< typename T


        , typename S = Sync


        , typename C = InitialCount<1> >


class RefCounter


  : public T


  , public S


{


  . . .



The bolt-in derives from the primary parameterizing type T and from the mixin policy type S, which defines the synchronization policy. The third parameter, C, defines the initial count policy. The synchronization policy defines locking functionality and atomic integer operations with the following interface:



struct S


{


// Atomic operations


  static int AtomicDecrement(int volatile &);


  static int AtomicIncrement(int volatile &);


// Locking operations


  void Lock();


  void Unlock();


};



There are four synchronization policy classes defined: SyncST, SyncMTNoLock, SyncMT, and the template SyncMTOuterLock, and preprocessor discrimination is used to define the typedef Sync to either SyncST or SyncMT depending on whether the compile environment is multithreaded. The last of the four policy types implements its locking by deferring to the composite class, which allows the bolt-ins to work with classes that already provide their own locking functionality, thereby saving space and potentially expensive synchronization objects. It is defined in Listing 22.3.[4]

[4] Note that this uses nonvirtual overriding (see section 22.3), but in these circumstances it is entirely safe since the template is used as a parameter to the RefCounter bolt-in template.

Listing 22.3.


template <class O>


struct SyncMTOuter


    : public SyncMTNoLock


{


  SyncMTOuter()


    : m_po(0)


  {}


  void Lock()


  {


    m_po->O::Lock();


  }


  void Unlock()


  {


    m_po->O::Unlock();


  }


  void InitSync(O *po)


  {


    m_po = po;


  }


  void UninitSync(O *po)


  {


    m_po = NULL;


  }


private:


  O *m_po;


};



The InitSync() and UninitSync() methods are then called within the bolt-in constructors and destructor, as shown in Listing 22.4:

Listing 22.4.


template< typename T


        , typename S = Sync


        , typename C = InitialCount<1> >


class RefCounter


    : public T


{


  RefCounter::RefCounter()


    : m_cRefs(C::Count)


  {


    InitSync(this);


  }


  RefCounter(RefCounter const &rhs)


    : ParentClass(rhs)


    , m_cRefs(C::Count)


  {


    InitSync(this);


  }


  ~RefCounter()


  {


    UninitSync(this);


  }


  . . .



That's great for SyncMTOuterLock, but what about the other synchronization policies? Well, one option would be to have them define InitSync() and UninitSync() methods, but that seems a bit ugly. The option I chose was to rely on the template resolution mechanisms and define the two free functions within the same namespace as the bolt-in classes:



inline void InitSync(void *)


{}


inline void UninitSync(void *)


{}



The template picks them up from outside its class definition (which includes all the types from which it is defined, including the synchronization mixins), and only "sees" the free functions when it cannot locate them internally. Now you can add any kind of synchronization class that you like and only define InitSync() and UninitSync() when you need to.


      Previous section   Next section