How to track memory assign by STL library

2020-07-24 03:37发布

问题:

I want to track all the memory(size allocated by std lib) allocated by all STL containers like map,list,vector etc. I just want to track STL container not regular object creation. Basically want to override new and delete of std lib.

Example

class demo  {
public:
  int i;
  std::list<int> mylist;
}

int main() {
demo dd = new demo(); // -> Don't want to track this. Just want to track
                      //    mylist(size of my list) 
}

I found out that std has it's own allocator option. For example list has it is allocator

template < class T, class Alloc = allocator<T> > class list;

What is the default allocator if I don't defined anything. I have thousand of list and none of then has allocator and I don't want to change each one of them manually. So, what I was thinking if there is way where I can replace default allocator with mine.

How to do this ?

回答1:

The default allocator for the standard containers is std::allocator, it is used for all standard containers (std::vector, std::list, etc) when an allocator is not provided.

To track allocations and deallocations you will have to create an allocator that you can use for tracking. You could use something like this:

template<typename _Ty>
struct MyAllocator
{
    typedef _Ty value_type;
    static _Ty* allocate(std::size_t n) 
    {    
        //Code that runs every allocation
        ...
        return std::allocator<_Ty>{}.allocate(n); 
    }

    static void deallocate(_Ty* mem, std::size_t n)
    {
        //Code that runs every deallocation
        ...
        std::allocator<_Ty>{}.deallocate(mem, n);
    }
};

MyAllocator mirrors std::allocator but it allows you to run some of your own code when an allocation occurs. What you want to put there is up to you.

There are two ways for you to make all of the containers use your allocator.

  1. You can replace all instances of std::list (or std::vector, std::map, etc.) with a template alias. For std::list the alias would look like this:

    template<typename _Ty>
    using MyList = std::list<_Ty, MyAllocator<_Ty>;
    

    Replace all instances of std::list with MyList. Now your allocator is used by all of your containers. To apply this to another container change list to the name of the container (Ex. for vector rename the alias to MyVector and change std::list to std::vector).

  2. If you are unable to edit the file or you don't want to modify it there is another option. You can use a macro to replace all instances of list with a class you have defined. This class will have to have been declared in namespace std and you will have to make sure to include <list> before setting up the macro. Setting it up for std::list would look like this:

    #include <list>
    
    namespace std
    {
        template<typename _Ty, typename _Alloc = MyAllocator<_Ty>>
        using tracked_list = list<_Ty, _Alloc>;
    }
    
    #define list tracked_list
    

    For a different container, change list to whatever container you want to replace (Ex. for vector change tracked_list to tracked_vector in both locations and replace list with vector in all three locations. Make sure that this code is before any other includes that could use std::list. If you put it in a header file, include that header before anything else. If it is in a source file, put it at the top of the file. This code will not override user supplied allocators, but it will make your allocator the default allocator.

    This method will change variable names, and that could affect your code. If possible, you should use method 1. However, if you have code that you can't change or code that is in external headers and this needs to be applied to that too, this method should work.



回答2:

If your main priority is short term low implementation cost (as opposed to longer term maintainability), one way to get around it is to wrap your demo class into an outer namespace that contains a std namespace. Inside outer::std you can redefine vector, list, etc. with your custom allocator. By doing so, all references to the std namespace will resolve to outer::std. The only work needed (in addition to implementing your custom allocator) is to wrap all your code into the outer namespace and to provide all the required definitions in outer::std. That part might easily become a pain. Future contributors might suffer.

Something like this:

#include <iostream>
#include <vector>
namespace outside
{
    template<typename T>
    struct MyAllocator
    {
    typedef typename std::allocator<T>::value_type value_type;
    typedef typename std::allocator<T>::pointer pointer;
    typedef typename std::allocator<T>::const_pointer const_pointer;
    typedef typename std::allocator<T>::reference reference;
    typedef typename std::allocator<T>::const_reference const_reference;
    typedef typename std::allocator<T>::size_type size_type;
    pointer allocate (size_type n, typename std::allocator<void>::const_pointer hint=0)
    {
        std::cerr << "allocate..." << std::endl;
        return std::allocator<T>{}.allocate(n, hint);
    }
    void deallocate (pointer p, size_type n)
    {
        std::cerr << "deallocate..." << std::endl;
        return std::allocator<T>{}.deallocate(p, n);
    }
    template <class Type> struct rebind
    {
        typedef MyAllocator<Type> other;
    };
    MyAllocator()
    {
    }
    MyAllocator(const MyAllocator<T>& other )
    {
    }
    template< class U >
    MyAllocator( const MyAllocator<U>& other )
    {
    }
};
} // namespace outside

namespace outer
{
namespace std
{
template<class T>
using vector = ::std::vector<T, outside::MyAllocator<T>>;
} // namespace std

class demo
{
public:
    int i;
    std::vector<int> myVector;
};
} // namespace outer

int main()
{
    using outer::demo;
    std::cerr << "creating new demo" << std::endl;
    demo *dd = new demo();
    std::cerr << "resizing dd->myList" << std::endl;
    dd->myVector.resize(10,3);
    delete dd;
}