I’m working on a small memory tool which tracks allocations and deallocations, object sizes, object types, and so on. The method I’m using to track source files, line numbers, and object types works like this:
#define DEBUG_NEW SourcePacket(__FILE__, __LINE__) * new
#define new DEBUG_NEW
SourcePacket is just a small class which takes a const char* and an int during construction. These values are populated via the __FILE__ and __LINE__ macros. The object type is acquired like so:
template<typename T>
T* operator*(const SourcePacket& packet, T* p);
p is the pointer to the newly allocated object, the type of which is discovered using RTTI. In the operator overload, the information is taken and stored in the tracer and the pointer is passed on to the program. Further info like the size and address is grabbed in the overloaded operator new.
Now, this setup has worked very well for me. It doesn’t work for code which I don’t compile, naturally, but one of the best things is that it plays nice with placement new calls made by the user, which didn’t work using the oft-cited
#define new new(__FILE__, __LINE__)
method. The problem I’ve encountered is that if the user calls operator new, the program simply can’t compile. Of course, this is because the macro expands like so
return operator SourcePacket("blahblah.cpp", 20) * new(size);
instead of
return SourcePacket("blahblah.cpp", 20) * new(size);
I can’t really see any way around this. I could, of course, just remove the SourcePacket * new procedure and just let my overloaded operator new collect the size and address, but that kind of defeats a large portion of the purpose of the tool.
(Also, just as a note, I’m not trying to create Valgrind or anything, and I know that overloading the global ops can be rather dodgy. This is mostly for educational purposes. In addition, I know that OS-specific features can be used to discover some of this information, but I would only like to use standard C++ in order for it to be cross-platform and bit-independent (x86, x64, etc.). So far it’s worked flawlessly for me on both Linux and Windows releases of both bit-flavors.)
Unfortunately, there doesn’t seem to be any way to conditionally use one way or the other, depending on if it’s just new (or placement new) or operator new. It’s not critical that I get this to work, but I would be interested to hear if anyone has found a way around this limitation.
We need an expression that’s valid when prefixed with “operator” and when not. This means we need to define an operator that takes a SourcePacket. It could take other arguments, but it turns out this isn’t necessary. Unary
operator *will do nicely:Since we can’t fully parenthesize the statement, there’s still the possibility of errors if used in all but the simplest expressions.
To resolve this, we need to define the appropriate operator. For the return type, you could define a hierarchy of template classes that pair a left argument with a SourcePacket and are commutative in the right argument (
(a:A ⊙ b:SourcePacket) * c:C) = (a:A ⊙ c:C) * b:SourcePacket, where ⊙ is some binary C++ operator). Something like the following, but without the bugs it undoubtedly possesses.The children of Paired could return a Result (
left ⊙ c, orleft ⊙ (right * c), which basically makes*(const SourcePacket&, T)right associative) rather than a Paired. For example: