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?