Search code examples
c++c++11make-shared

c++ std::make_shared with new macro


I use a macro in place of new to get some extra information in debug mode:

#if defined(_DEBUG)
#define SAGE_NEW new(__FUNCTION__, __FILE__, __LINE__)
#else
#define SAGE_NEW new
#endif

I have found this quite useful in custom memory profiling and memory leak detection. I just started using shared pointers, so now I am making heap objects like:

auto myThingy = std::shared_ptr<Thingy>(SAGE_NEW Thingy(Args) );

I have just learned that std::make_shared is preferred because it uses fewer memory allocations. Is there any way I can include my SAGE_NEW in make_shared? I realize it won't matter for leak detection but I would still like it for memory usage statistics. It seems like allocate_shared somehow holds the answer but I haven't figured it out. Thanks! :)

Edit : To those asking about new - I overload it with a custom new. A compiler option SAGE_MEM_INFO turns on leak detection and memory usage stats, otherwise it skips logging and goes directly to my memory pool allocation. I have new[] and delete[] variants but I'm omitting those for brevity:

#ifdef SAGE_MEM_INFO
void* operator new  (size_t size){ ++appAllocs; return myAlloc(size); }
void* operator new  (size_t size, char const *function, char const *filename, int lineNum)
{
    ... Log memory usage
    void* ptr = ::operator new(size);
    return ptr;
}
void  operator delete   (void* ptr)
{
    ... Log freeing of this pointer
    --appAllocs;
    myFree(ptr);
}
void  operator delete   (void* ptr, char const *function, char const *filename, int lineNum)
{
    ... Log freeing of this pointer
    --appAllocs;
    myFree(ptr);
}
#else
void* operator new  (size_t size){ return myAlloc(size); }
void* operator new  (size_t size, char const *function, char const *filename, int lineNum)
{
    void* ptr = ::operator new(size);
    return ptr;
}
void  operator delete  (void* ptr) { myFree(ptr); }
void  operator delete  (void* ptr, char const *function, char const *filename, int lineNum) { myFree(ptr); } 
#endif

Solution

  • Yes, you can certainly do so.

    Still, you have to choose your poison:

    • Use an allocator-type which is not empty, but saves at least a pointer.
    • Use a new allocator-type for each allocation, which will be reflected in a new polymorphic type for the reference-count.

    http://en.cppreference.com/w/cpp/concept/Allocator shows the requirements, and a good minimal allocator declaration.

    Adapted std::allocator here for the first option:

    #if defined(_DEBUG)
    template <class Tp>
    struct DebugLinesAllocator : std::allocator<Tp> {
      const char* func, *file;
      int line;
      Tp* allocate(std::size_t n, const void* = 0)
      {return ::operator new(n * sizeof(T), func, file, line);}
      template< class U > struct rebind { typedef DebugLinesAllocator<U> other; };
      DebugLinesAllocator(const char* func, const char* file, int line)
      : func(func), file(file), line(line) {}
      template< class U > DebugLinesAllocator( const DebugLinesAllocator<U>& other )
      : func(other->func), file(other->file), line(other->line) {}
    };
    #define SAGE_MAKE_SHARED(type, ...) allocate_shared<type>(DebugLinesAllocator<type>\
        {__FUNCTION__, __FILE__, __LINE__}, __VA_ARGS__)
    #else
    #define SAGE_MAKE_SHARED(type, ...) make_shared<type>(__VA_ARGS__)
    #endif
    

    Still, its far less useful for shared-pointers. Anyway, every little bit may help.

    Use it like

    auto p = SAGE_MAKE_SHARED(my_type, my_argument_1, ...);