3

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
DSM
  • 99
  • 6
  • A quick comment: people often mention the memory allocations for make_shared. This is true, but this is a one-time cost (per pointer). Another very good reason is that when you use make_shared, the object itself and its reference count (and possibly other stuff) all get put into a contiguous block of memory. This results in better cache behavior for copying and deleting copies, an operation that might be repeated many times per pointer created. – Nir Friedman Sep 20 '14 at 18:51
  • If you have a solution, write it as an answer please. The question box is for, uhm, your _question_. – Lightness Races in Orbit Sep 20 '14 at 22:38
  • For you original macros, shown above, you need to remember to define a corresponding placement `operator delete`, because that's called by the placement new expression if the constructor throws. without it you get a memory leak, the very thing you're out to eliminate. it was an infamous MFC bug once. – Cheers and hth. - Alf Sep 20 '14 at 22:54
  • I have never seen syntax like `new(__FUNCTION__, __FILE__, __LINE__)`. Can you point me to a reference of what is going on? http://www.cplusplus.com/reference/new/operator%20new/ makes no reference to doing something like that for debugging. Do you have an overload of operator new somewhere which accepts the extra parameters? – steveire Sep 21 '14 at 08:07

3 Answers3

1

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, ...);
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
  • You don't need `rebind` in C++11. – Puppy Sep 20 '14 at 18:21
  • @Puppy: Still, the base-class might have it, so I better have it too. (Might remember that for times I don't add to `std::allocator`) – Deduplicator Sep 20 '14 at 18:24
  • Thanks. Seems to work but I added a static_cast, explicitly wrote the type of __FUNCTION__, etc. See my edited answer. Thanks again! – DSM Sep 20 '14 at 22:29
  • @DSM: Added the rebind+ctor quite late, forgot to also add the complicated ctor there. The rest, ... dunno. – Deduplicator Sep 20 '14 at 22:37
  • using a custom allocation function (a placement new operator) for allocation, it would be a good idea to also use a corresponding deallocation function. typically the implementation of a debugging allocation like here is to store the info at the start of a slightly larger block than requested, then return a pointer to the part after the info. the ordinary deallocation function won't be able to deal with that (or else it won't be able to deal with ordinary allocated blocks, either way ungood). – Cheers and hth. - Alf Sep 20 '14 at 22:51
0

You can create your own makeShared where you will do whatever bookkeeping you need and afterwards you will call the real make_shared.

ixSci
  • 13,100
  • 5
  • 45
  • 79
0

Posting in answers rather than the question...

My compiler didn't like some things in Deduplicator's answer. Below doesn't seem to be completely wrong:

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

Please let me know if you think any of my changes are suspect.

DSM
  • 99
  • 6