Previous section   Next section

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


22.6. Parameterized Polymorphic Packaging

The classic trade-off between efficiency and flexibility has no clearer case than in whether to make a class polymorphic. It's an argument that can't be won, and it would be far nicer not to have to make the decision. Using bolt-ins, we can do just that in many cases, such that the decision may be postponed to where it belongs: at the manifestation of the programmer's design.

Consider a class used for locking via mutual exclusion between the threads of a single process, shown in Listing 22.5.

Listing 22.5.



class ThreadMutex


{


public:


  ThreadMutex();


  ~ThreadMutex();


// Operations


public:


  void Lock();


  void Unlock();


// Members


private:


  . . . // platform-specific data


};



For efficiency, no methods are declared virtual, and the Lock() and Unlock() methods are inlined. That works fine, and is maximally efficient, but it constrains the behavior of the code that uses ThreadMutex to the decisions made when the class was written. There is still a lot of flexibility in this model, for example, you might also define ProcessMutex and NoopMutex classes, but the flexibility is at compile time, leveraged by selecting one or the other of the types, perhaps via preprocessor selection.

Alternatively, if you needed run time flexibility, you could define an interface IMutex, wherein Lock() and Unlock() would be declared virtual, as in:



struct IMutex


{


  virtual void Lock() = 0;


  virtual void Unlock() = 0;


};



You would then code in the following way:



IMutex  *mx = FastLookupMutex(API_ID_XYZ);


mx->Lock();


XYZFunction(); // from the XYZ API


mx->Unlock();



Now the actual concrete mutex class would be locked and unlocked via the vtable. (In the real world, we'd scope the Lock() and Unlock() calls; see section 6.2). But now we've got two versions of code. While some operating systems have simple and obvious synchronization APIs, others do not, so it's maintenance headache time.

(By the way, I know—from bitter personal experience—that using synchronization objects polymorphically is likely to have a nontrivial overhead. Pondering this might help you avoid the embarrassment I had a few years when I foisted a similar polymorphic version of this on an unsuspecting client: it accounted for 15 percent of processor time until I realized my error and switched it. They were very happy and congratulated me for my expertise; I was most embarrassed to admit what I'd done. Some consultant![7])

[7] They actually appreciated my honesty in coming clean. Lesson: always be honest; it can earn you friends in this industry where responsibility takers are few and far between.

So what's to be done? How can we get polymorphic flexibility when we want it, and efficient inflexibility when we want it? Answer: bolt-ins and a dash of EBO (see section 12.3).

We can redefine our THReadMutex class to be a template, thus:



template <typename I>


class ThreadMutex


  : public I


{


  . . .



Now ThreadMutex derives from I, whatever I may be; nothing else in the class definition changes. If we want to have a thread mutex that is polymorphic with respect to IMutex, we use the ThreadMutex<IMutex> parameterization.

If we want a nonpolymorphic version, we would parameterize with a null type such as boost::null_t, which adds no data, no methods, and no vtable/vptr (see Chapters 7 and 8). Since most compilers employ EBO very well, we'll have a type that is physically identical to the original definition of ThreadMutex. Even with those few that do not (see Table 18.3), it is a benign flaw, since it's the run time cost of vtable indirection that we're most concerned about; we're hardly likely to be creating thousands of ThreadMutex<boost::null_t> instances.

This is as big a bolt-in as you can get. In the composite type ThreadMutex<boost::null_t>, ThreadMutex<> provides everything: data, methods, and type (apart from the fatuous ability to cast to boost::null_t&).


      Previous section   Next section