Any way to avoid using a macro for this C++ scoped logger?

Viewed 218

I am refreshing some very old code in our diagnostic library to modern C++, and one piece of code remains that has always bothered me. It is a very common kind of scoped logging function that uses a macro to define the storage of logging data in an application-specific slot, simplified here as an int, with an optional version that takes a condition which is used to decide whether the timing should happen (for specific cases like first time through a loop, or when under extreme artificial external timing pressure, for example).

class ScopedTimer
{
   int mSlot;
   bool mCondition;
   Ticker mBeginTimer;
public:
  ScopedTimer( int slot, bool condition ) : 
      mSlot( slot ), 
      mCondition(condition)
   {
       if ( mCondition ) mBeginTimer = GetTimer(); 
   }

  ~ScopedTimer() 
  { 
       if (mCondition)
       {
           StoreInSlot( mSlot, GetTimer() - mBeginTimer ); 
       }
  }
};

#ifdef PROFILING_ENABLED
#define ST_NAME_CONCAT( x, y )      x ## _ ## y
#define ST_NAME_EVALUATOR( x, y ) ST_NAME_CONCAT( x, y )
#define ST_NAME( prefix ) ST_NAME_EVALUATOR( prefix, __LINE__ )
#define SCOPED_TIMER(slot) ScopedTimer ST_NAME(scopedTimer)( slot, condition )
#else
#define SCOPED_TIMER(slot) // goes away in release
#endif

and in use:

extern bool ConditionRequestsTiming();

void foo()
{
  SCOPED_TIMER( some_magic_slot_number, ConditionRequestsTiming() );

  ... other operations

}

Expands to

void foo()
{
  ScopedTimer scopedTimer12( some_magic_slot_number, ConditionRequestsTiming() );

  ... other operations

  // calling ~ScopedTimer() here, but only if ConditionRequestsTiming was true, storing time difference in the slot
}

And in release:

void foo()
{
  ... other operations
}

Which of course works as expected, giving a scoped timer for the method. It is very convenient, and less error prone, but it doesn't respect namespaces and that's always bothered me.

Any thoughts on a modern solution without too much complication, or should I just accept this pattern as okay for our diagnostic library?

2 Answers

Usually people use macros to append __LINE__ to the declaration to allow for multiple declarations in one scope block.

Prior to C++20 this was impossible without macros. C++20 and later, with a little work, can use std::source_location.

Jason Turner has a video on it in his C++ weekly video series here

I was initially not sure to grasp the advantage of that macro, apart hiding the timer instance name (and cause possible conflicts). But I think that the intent could be to have the possibility to do this:

#ifdef _DEBUG
  #define SCOPED_TIMER(slot) ScopedTimer __scopedTimer( slot );
#else
  #define SCOPED_TIMER(slot) ;
#endif

That would indeed save some keystrokes; otherwise, if the timing takes place also in release builds, I would simply use directly the macro definition; either way, I would get rid of the initial underscores in the object name (that are conventionally reserved for compiler implementers):

ScopedTimer scoped_timer( some_magic_slot_number );
Related