I've written a memory tracker for use in my game engine. I based it on [this slide][1] in a talk by Nicolai Josuttis.

One problem I'm having is that, during reporting, the implicit string conversion and output stream operator functions allocate. This results in calls to `new`. Since I've overloaded `new` the tracker tries to track the new allocations and ends up invalidating the results.

**Is there a way to output the results without allocating?**

Memory.hpp

    #pragma once
    //https://www.youtube.com/watch?v=e2ZQyYr0Oi0
    //C++17 - The Best Features - Nicolai Josuttis [ACCU 2018]

    #include "Engine/Core/BuildConfig.hpp"
    #include "Engine/Core/ErrorWarningAssert.hpp"

    //#include "Engine/Profiling/StackTrace.hpp"

    #include <new>
    #include <sstream>
    #include <iostream>
    #include <ostream>

    class Memory {
    public:

        struct status_t {
            std::size_t leaked_objs  = 0;
            std::size_t leaked_bytes = 0;
            operator bool() const noexcept {
                return leaked_objs || leaked_bytes;
            }
            operator std::string() const noexcept {
    #ifdef TRACK_MEMORY
                std::ostringstream ss;
                std::string s = ss.str();
                ss << "Leaked objects: " << leaked_objs << " for " << leaked_bytes << " bytes.\n";
                return s;
    #else
                return {};
    #endif
            }
            friend std::ostream& operator<<(std::ostream& os, [[maybe_unused]]const status_t& s) noexcept {
    #ifdef TRACK_MEMORY
                os << "Leaked objects: " << s.leaked_objs << " for " << s.leaked_bytes << " bytes.\n";
    #endif
                return os;
            }
        };
        struct status_frame_t {
            std::size_t frame_id = 0;
            std::size_t leaked_objs = 0;
            std::size_t leaked_bytes = 0;
            operator bool() const noexcept {
                return leaked_objs || leaked_bytes;
            }
            operator std::string() const noexcept {
    #ifdef TRACK_MEMORY
                std::ostringstream ss;
                ss << "Frame " << frame_id << ": Leaked objects: " << leaked_objs << " for " << leaked_bytes << " bytes.\n";
                std::string s = ss.str();
                return s;
    #else
                return {};
    #endif
            }
            friend std::ostream& operator<<(std::ostream& os, [[maybe_unused]]const status_frame_t& s) noexcept {
    #ifdef TRACK_MEMORY
                os << "Frame " << s.frame_id << ": Leaked objects: " << s.leaked_objs << " for " << s.leaked_bytes << " bytes.\n";
    #endif
                return os;
            }
        };

        [[nodiscard]] static void* allocate(std::size_t n) noexcept {
            if(is_enabled()) {
                ++frameCount;
                frameSize += n;
                ++allocCount;
                allocSize += n;
                if(maxSize < allocSize) {
                    maxSize = allocSize;
                }
                if(maxCount < allocCount) {
                    maxCount = allocCount;
                }
            }
            return std::malloc(n);
        }

        static void deallocate(void* ptr, std::size_t size) noexcept {
            if(is_enabled()) {
                ++framefreeCount;
                framefreeSize += size;
                ++freeCount;
                freeSize += size;
            }
            std::free(ptr);
        }

        static void enable([[maybe_unused]]bool e) noexcept {
    #ifdef TRACK_MEMORY
            _active = e;
    #endif
        }

        static bool is_enabled() noexcept {
    #ifdef TRACK_MEMORY
            return _active;
    #else
            return false;
    #endif
        }

        static void trace([[maybe_unused]]bool doTrace) noexcept {
    #ifdef TRACK_MEMORY
            _trace = doTrace;
    #endif
        }

        static void tick() noexcept {
    #ifdef TRACK_MEMORY
            if(auto f = Memory::frame_status()) {
                std::cout << f << '\n';
            }
            ++frameCounter;
            resetframecounters();
    #endif
        }

        static void resetframecounters() noexcept {
    #ifdef TRACK_MEMORY
            frameSize = 0;
            frameCount = 0;
            framefreeCount = 0;
            framefreeSize = 0;
    #endif
        }

        static status_t status() noexcept {
            return { allocCount - freeCount, allocSize - freeSize };
        }

        static status_frame_t frame_status() noexcept {
            return { frameCounter, frameCount - framefreeCount, frameSize - framefreeSize };
        }

        inline static std::size_t maxSize = 0;
        inline static std::size_t maxCount = 0;
        inline static std::size_t allocSize = 0;
        inline static std::size_t allocCount = 0;
        inline static std::size_t frameSize = 0;
        inline static std::size_t frameCount = 0;
        inline static std::size_t frameCounter = 0;
        inline static std::size_t freeCount = 0;
        inline static std::size_t freeSize = 0;
        inline static std::size_t framefreeCount = 0;
        inline static std::size_t framefreeSize = 0;
    protected:
    private:
        inline static bool _active = false;
        inline static bool _trace = false;
    };

    #ifdef TRACK_MEMORY

    void* operator new(std::size_t size);
    void* operator new[](std::size_t size);
    void operator delete(void* ptr, std::size_t size) noexcept;
    void operator delete[](void* ptr, std::size_t size) noexcept;

    #endif


Memory.cpp

    #include "Engine/Profiling/Memory.hpp"

    #ifdef TRACK_MEMORY

    void* operator new(std::size_t size) {
        return Memory::allocate(size);
    }

    void* operator new[](std::size_t size) {
        return Memory::allocate(size);
    }

    void operator delete(void* ptr, std::size_t size) noexcept {
        Memory::deallocate(ptr, size);
    }

    void operator delete[](void* ptr, std::size_t size) noexcept {
        Memory::deallocate(ptr, size);
    }

    #endif


  [1]: https://www.youtube.com/watch?v=e2ZQyYr0Oi0&t=1m11s