I decided to overload the new, new[],... operators in my classes so I can log the file and line at which they were called so I can easier track memory allocations/leaks.
Now the problems is in my stack and array classes (and other template container classes which allocate memory):
If I use them with one of my classes which has the new,new[],... operators overloaded it works fine.
But if I use it with the standard c++ data types (int,float,...) I can't allocate them, since no overloaded new operator matches the arguments of the new(__ LINE __ , __ FILE __) operator (or others like placement new).
Example of stack code:
// placement new
T* t=new(__ LINE __ , __ FILE__)(&m_data[i])T;
So I'm out of good ideas on how to make this work. If I replace new(__ LINE __ ,__ FILE __) with new I loose memory logging ability. One solution is to make a separated stack for standard data types in which the default new is used.
Is there any way to detect at compile time if a template parameter is a struct, class or a built in c++ type?
How do you handle stuff like this? What do you suggest? Any comments on this design (good,bad) are obviously welcome (just don't post stuff like "don't reinvent the wheel with your own containers ").
New and Delete operators can be overloaded globally or they can be overloaded for specific classes. If these operators are overloaded using member function for a class, it means that these operators are overloaded only for that specific class.
The most common reason to overload new and delete are simply to check for memory leaks, and memory usage stats. Note that "memory leak" is usually generalized to memory errors. You can check for things such as double deletes and buffer overruns.
When a binary operator is overloaded the corresponding assignment operator, if any, must be explicitly overloaded. We can use the default equality operator in an overloaded implementation of the equality operator. A public or nested public reference type does not overload the equality operator.
The first parameter is always an ostream object (we've mostly used cout, so far). Because of this, it cannot be defined as a member function (it would have to be a member of the ostream class, which we cannot change). The << and >> operators should always be defined as outside functions (usually friend functions).
Please note that your current solution requires adding the logging code to every new(line, file)
overload you have. Also, you cannot easily switch it off in release builds unless you surround each of your logging calls within #ifndef DEBUG ... #endif
.
Here's one way of achieving what you wnat: Instead of overloading the new
operator for each of your classes, consider overloading the global new
operator using the placement syntax; that way you avoid interfering with the 'normal' new
operator. Then you can #define
new and delete macros for convenience and, most importantly, you can have control over when your memory-tracking new/delete
is applied and when the standard version is used.
#ifdef ENABLE_CUSTOM_ALLOC
// Custom new operator. Do your memory logging here.
void* operator new (size_t size, char* file, unsigned int line)
{
void* x = malloc(size);
cout << "Allocated " << size << " byte(s) at address " << x
<< " in " << file << ":" << line << endl;
return x;
}
// You must override the default delete operator to detect all deallocations
void operator delete (void* p)
{
free(p);
cout << "Freed memory at address " << p << endl;
}
// You also should provide an overload with the same arguments as your
// placement new. This would be called in case the constructor of the
// created object would throw.
void operator delete (void* p, char* file, unsigned int line)
{
free(p);
cout << "Freed memory at address " << p << endl;
}
#define new new(__FILE__, __LINE__)
#endif
// A test class with constructors and destructor
class X
{
public:
X() { cout << "X::ctor()" << endl; }
X(int x) { cout << "X::ctor(" << x << ")" << endl; }
~X() { cout << "X::dtor()" << endl; }
};
int main (int argc, char* argv[])
{
X* x3 = new X();
X* x4 = new X(20);
delete x3;
delete x4;
}
you should see something like:
Allocated 1 byte(s) at address 00345008 in Alloc.cpp:58
X::ctor()
Allocated 1 byte(s) at address 003450B0 in Alloc.cpp:59
X::ctor(20)
X::dtor()
Freed memory at address 00345008
X::dtor()
Freed memory at address 003450B0
Try substituting X
for int
and you'll see it works too. You can extend this to the array and placement new as well but i'd rather not make the post longer than it is.
Few last pointers at the end:
- MSVC has this functionality, see here
- There is a toturial about doing memory tracking this way here under the 'Tracing Memory Leaks' section
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With