I decided to try my hand at making my code a bit safer as far as managing weak references goes, so I tried to make something to track references to memory locations. I soon thought that it was a stupid idea and simplified it so that it was simply the memory locations' validity which were being monitored. That way, all I needed to do was essentially a custom null check to see if my weak references were still valid. I tried to do this in the simplest and most aesthetically pleasing way as possible. Naturally, that means (to me) attempting to overload the new and delete keywords for every possible type. Well, since you can't do that, I decided to use macros :)
I also came up with two seperate static classes which are only really different in that they use two different data structures.
- Import the .cpp files into your project source and the .h files into your project headers.
- Include only the MemMon.h file wherever you plan on using it.
- Inside of MemMon.h is where you set your desired implementation (map, set, or none).
- Find the macro definition for MEMMON_TYPE and set it to one of MAPMEMMON_TYPE, SETMEMMON_TYPE, or NULL_MEMMON_TYPE.
- From now on, instead of new, delete, new[], and delete[] keywords, use mm_new, mm_delete, mm_new_a, and mm_delete_a, respectively.
MemMon new expects to be passed a constructor of the type to allocate.
Example usage: Foo *foo = mm_new(Foo(args));
MemMon new array expects to be passed a type and the number of elements of that type to allocate.
Example usage: Foo *foo_arr = mm_new_a(Foo, 25);
MemMon delete expects to be passed a pointer to the data to delete.
Example usage: mm_delete(foo);
MemMon delete array expects to be passed a pointer to the data to delete where the data was allocated as an array.
Example usage: mm_delete_a(foo_arr);
MemMon null check expects to be passed a pointer to the data to check if that data is valid or null.
Example usage: bool dataIsNull = mm_null(foo);
MemMon size expects to be passed a pointer to some data and will check how many bytes were allocated to that address. This works effectively on arrays allocated with mm_new_a just as well as single chunks of data allocated with mm_new. This macro is only available with MEMMON_TYPE set to MAPMEMMON_TYPE.
Example usage: size_t foo_size = mm_sizeof(foo);
MemMon element count expects to be passed a pointer to some data and will treat that data as an array, checking how many elements were allocated to the array. Note, this macro is really for arrays, but can be used with regular pointers allocated with mm_new - the output in that case will always be 1. This macro is only available with MEMMON_TYPE set to MAPMEMMON_TYPE.
Example usage: size_t foo_arr_count = mm_count(foo_arr);
This one represents my first iteration on this idea. Using a set, it simply keeps track of which memory locations are still not freed. Although the next iteration is more useful, I kept this one around just because of the slight speed compromise.
Using a map, I stored a pointer to the address as the key and the number of bytes to be stored as the value. This way, I could ALSO perform a sizeof on any dynamically allocated arrays, and expanding on that, an element count as well! I am happy with the results and think that I will likely be using MapMemMon all the time in the future.
So for performance testing, among other stress tests, I basically just did a bunch of new array and delete array calls in a loop and compared the clock times with regular new[] and delete[] calls. I would say I am happy enough with the results, given the overhead. Should there be any curiosity, the following is the average of my results for a run of speedtest.cpp:
- Regular new[] and delete[]: 160 clicks for 1000 allocated and deleted Foo arrays of size 1000
- SetMemMon new[] and delete[]: 370 clicks for 1000 allocated and deleted Foo arrays of size 1000
- MapMemMon new[] and delete[]: 580 clicks for 1000 allocated and deleted Foo arrays of size 1000