Previous section   Next section

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


11.4. Static Members

It would be churlish to leave a chapter on statics without covering static members, so let's do that now. Some of the issues in this section are new; others are a look at issues raised in earlier chapters.

11.4.1 Heading Off Linker Issues

Sometimes, you will be coding to library classes or functions that never change for the life of the process, or for the life of the current user or system session, or even for the life of the system installation. In such cases, it is undesirable to call into these components each time you want to access the constant information, especially when the information is itself potentially quite expensive.

A good example of this is the Win32 high performance counter API, which consists of two functions:



BOOL QueryPerformanceCounter(LARGE_INTEGER *);


BOOL QueryPerformanceFrequency(LARGE_INTEGER *);



Each takes a pointer to a 64-bit integer. The first returns the current value of the system's hardware high-performance counter. The second returns the counter's frequency, which cannot change during the lifetime of the system session and, in practice, is constant for a given processor. The frequency is used to translate the machine specific epochs returned by QueryPerformanceCounter() into actual time intervals.

Naturally, my C++ instincts bristle at using a naked API such as this, so a wrapper class got written the second time I had to use it.[12] The high_performance_counter class maintains two integers that represent a measured interval (acquired by calls to start() and stop() methods), and provides methods to interpret the interval in seconds, milliseconds, or microseconds. The problem is that both these API functions have quite a large call cost in and of themselves [Wils2003a], so it's prudent to avoid the unnecessary repeated calls to QueryPerformanceFrequency() by caching the value. This is the classic static member situation. However, since one of the guiding principles of STLSoft is to be 100% header-only,[13] this represents a challenge. The way to provide it is to use a function-local static instance, from within a static method, as in:

[12] Robert L. Glass [Glas2003] makes a compelling case for avoiding premature generalization by deferring the conversion of code from specific to reusable until the second time you need it, and I tend to agree.

[13] I should come clean and admit that I have a personal, and probably dogmatic, bias against implementation files that are needed only for static members. In calm moments, I wonder whether this is rationale, but I just can't seem to shake my love of the unfettered "atomic" #include. Whether rationale or not, it serves to provide much material for this chapter that (I hope) is germane to the topic of statics.



class high_performance_counter;


{


  . . .


private:


  static LARGE_INTEGER const &frequency()


  {


    static LARGE_INTEGER s_frequency;


    return s_frequency;


  }


  . . .



But that's only half the picture; it's not yet initialized. The answer is to initializes_frequency with a call to another static method to retrieve the frequency value, as in Listing 11.11.

Listing 11.11.


class high_performance_counter;


{


  . . .


  static LARGE_INTEGER const query_frequency()


  {


    LARGE_INTEGER freq;


    if(!QueryPerformanceFrequency(&freq))


    {


      freq = std::numeric_traits<sint64_t>::max();


    }


    return freq;


  }


  static LARGE_INTEGER const &frequency()


  {


    static LARGE_INTEGER s_frequency = query_frequency();


    . . .



Note that if the system does not support a hardware performance counter, and QueryPerformanceCounter() returns FALSE, then the value is set to the maximum value, so that subsequent division of the interval will not result in a divide by zero error, but will just return 0. I chose this approach because these performance measurement classes are intended for code profiling rather than as part of application functionality; you might wish to have it throw an exception.

But we're not quite there yet, since we're using a function-local static, and they are generally subject to races. In this case, the worst that can happen is that the query_frequency() may be called multiple times in extremely rare circumstances. Since it always returns the same value, and we need the performance counter class itself to be as low-cost as possible, I chose to risk the very rare, but benign, multiple initialization.

11.4.2 Adaptive Code

Before we finish the subject of static members, I'd just like to show you a final sneaky trick,[14] which allows you to write classes that can adapt their own behavior to the circumstances in which they find themselves.

[14] I don't show these things to encourage the arrogant, rebel-without-a-cause attitude that far too many software engineers wear with pride. But I don't believe that hiding complex and dangerous things from people helps much either; look at Java! I show you these things to inform, and because seeing a wrong way can often be as valuable in learning as seeing a right way.

The performance counter class described in the last section had a problem in that if the hardware counter were unavailable the user of the class would be left with useless timings all reading 0. In fact, Win32 supports other timing functions, one of which, GetTickCounter(), is available on all platforms, but has much lower resolution [Wils2003a]. Hence, another class, performance_counter, attempts to provide the high-resolution measurements where possible, and the low-resolution ones where not.

To do this, it eschews direct calls of QueryPerformanceCounter() in favor of calling a static method measure(), defined as shown in Listing 11.12.

Listing 11.12.


class performance_counter


{


  . . .


private:


  typedef void (*measure_fn_type)(epoch_type&);


  static void performance_counter::qpc(epoch_type &epoch)


  {


    QueryPerformanceCounter(&epoch);


  }


  static void performance_counter::gtc(epoch_type &epoch)


  {


    epoch = GetTickCount();


  }


  static measure_fn_type get_measure_fn()


  {


    measure_fn_type fn;


    epoch_type      freq;


    fn = QueryPerformanceFrequency(&freq) ? qpc : gtc;


    return fn;


  }


  static void measure(epoch_type &epoch)


  {


    static measure_fn_type  fn  =   get_measure_fn();


    fn(epoch);


  }


// Operations


public:


  inline void performance_counter::start()


  {


    measure(m_start);


  }


inline void performance_counter::stop();


  . . .



The frequency() and query_frequency() methods are the same as before, except that failure from QueryPerformanceFrequency() causes the frequency to be set to 1,000, to reflect the fact that GetTickCounter() returns millisecond units and gives valid interval divisions.

Once again, race conditions are benign, so they're ignored in favor of keeping the normal call costs of the counter class as low as possible.


      Previous section   Next section