// Original C++ file
// C++ tip of the week #11
// Topic: Catching memory leaks (1)
// Relevance: debugging
//

#if defined( _MSC_VER ) && !defined( HAVE_NEW_IOSTREAMS )
#define HAVE_NEW_IOSTREAMS
#endif

#if __GNUC__ >= 3
#define HAVE_NEW_IOSTREAMS
#endif

#ifdef HAVE_NEW_IOSTREAMS
#include <iostream>
#else
#include <iostream.h>
#define std
#endif

#ifndef NDEBUG

#include <map>

#include <assert.h>
#include <stdio.h>

// pointers to objects allocated on the heap will be stored, together with
// file name and line info of where they were 'new-ed'
typedef std::map< void*, std::pair< const char*, int > > ObjLocMap_t;

// helper class to count heap based objects
class ObjectCounter {
   const char* _szClassName; // class name for which the counter is counting

public:
   explicit ObjectCounter( const char* szClassName );
   ~ObjectCounter();

   int _nCount;                   // separate object count
   ObjLocMap_t _mapObjectWasHere; // only if overloaded operator new is used
};

ObjectCounter::ObjectCounter( const char* szClassName ) : _nCount( 0 ),
   _szClassName( szClassName ) {}

// upon destruction, ObjectCounter 'detects' if any objects are not destroyed,
// printf is used, as on some platforms std::cout might already be destroyed (2)
ObjectCounter::~ObjectCounter() {
   assert( _nCount >= _mapObjectWasHere.size() );
   if ( _nCount ) {
      printf( "%s: %d objects leaked!\n", _szClassName, _nCount );
   }
   if ( _mapObjectWasHere.size() ) {
      printf( "These objects where allocated in:\n");
      for ( ObjLocMap_t::iterator objIdx = _mapObjectWasHere.begin();
	    objIdx != _mapObjectWasHere.end(); ++objIdx ) {
	 printf( " file: %s at line: %d\n",
		 objIdx->second.first, objIdx->second.second );
      }
   }
}

// this trace strategy depends on ObjectCounter to be destroyed only after all
// heap based objects of 'Class' are destroyed; note also, that 'Class', not
// ObjectCounter, manages the object count
#define DECLARE_TRACE_OBJECTS( Class )                                         \
   static ObjectCounter W130798Count; /* weird name, prevents conflicts */     \
public:                                                                        \
   /* see ctotw8.cxx ("Memory Pool") for comments on class specific memory
      allocation and deallocation operators */                                 \
   void* operator new( size_t size ) {                                         \
      assert( sizeof( Class ) == size );                                       \
      ++W130798Count._nCount; /* one new heap based object */                  \
      return ::operator new( size );                                           \
   }                                                                           \
   void* operator new( size_t size, const char* str, int l ) {                 \
      void* p = Class::operator new( size ); /* updates count */               \
      ObjLocMap_t& theMap = W130798Count._mapObjectWasHere;                    \
      assert( theMap.end() == theMap.find( p ) );                              \
      theMap[ p ] = std::make_pair< const char*, int >( str, l );              \
      return p;                                                                \
   }                                                                           \
   void* operator new( size_t, void* p ) throw() { /* placement new */         \
      /* no W130798Count._nCount++, as no memory is allocated */               \
      return p;                                                                \
   }                                                                           \
   void operator delete( void* p, size_t size ) {                              \
      if ( p == 0 ) return;                                                    \
      assert( sizeof( Class ) == size );                                       \
      --W130798Count._nCount; /* one heap based object less */                 \
      ObjLocMap_t& theMap = W130798Count._mapObjectWasHere;                    \
      ObjLocMap_t::iterator objIdx;                                            \
      /* objects need not be logged in the object location map */              \
      if ( theMap.end() != ( objIdx = theMap.find( p ) ) ) {                   \
         theMap.erase( objIdx );                                               \
      }                                                                        \
      ::operator delete( p );                                                  \
   }

// the ObjectCounter is static, thus it needs to be defined (3)
#define IMPLEMENT_TRACE_OBJECTS( Class )                                       \
   ObjectCounter Class::W130798Count = ObjectCounter( #Class );
#else // NDEBUG
// if in production mode, the tracing code is removed
#define DECLARE_TRACE_OBJECTS( Class )
#define IMPLEMENT_TRACE_OBJECTS( Class )
#endif // !NDEBUG

class Test {
   DECLARE_TRACE_OBJECTS( Test )
public:
   Test() { std::cout << "A Test object was created." << std::endl; }
   ~Test() { std::cout << "A Test object was destroyed." << std::endl; }
};

IMPLEMENT_TRACE_OBJECTS( Test )

// this macro need not be used if heap objects need only be counted; it should
// be added after all header files only
#ifndef NDEBUG
#define new new( __FILE__, __LINE__ )
#endif


// This program will show the use of the above defined memory leak catching
// macro's by explicitly leaking a heap based object.

int main() {
   Test myFirstTest;
   Test* pMySecondTest = new Test; // (4)
   Test* pMyThirdTest = new Test;
   delete pMyThirdTest; // (4)

   return 0;
}

// (1) In general, professional tools such as Insure++ or the 'built-in'
// tools of your IDE should be preferred over home crafted gadgets like these
// macro's. This example is however instructive, as memory leak detection tools
// employ similar techniques.
//
// (2) Which is not standard conforming behaviour, see footnote #265.
//
// (3) For classes inside namespaces the fully qualified name needs to be used,
// for templates a special macro needs to be written which adds the
// template< class T > upfront.
//
// (4) The tracing scheme is easily defeated by using ::new or ::delete.