Search code examples
c++stdstringallocator

How to define a custom mock allocator?


I'm trying to define a custom std::basic_string specialization with a mock allocator to log all memory operations that basic_string performs.

struct MockAllocator : std::allocator<char> {
    char* allocate(size_t n);
    void deallocate(char *p, size_t n);
};

using CustomString = std::basic_string<char, std::char_traits<char>, MockAllocator>;

CustomString str("Hello World........ (more symbols to avoid SSO)");

This simple code doesn't call methods of my allocator. I can even skip definitions, and linker doesn't produce any errors. What am I doing wrong?


Solution

  • The following assumes C++11 (and later) allocator semantics, which older compilers (even if they did otherwise implement C++11) may not have implemented fully yet.


    Your type doesn't satisfy the allocator requirements because it doesn't rebind properly and so there is no guarantee how the code will behave.

    A minimal stateless allocator looks like this:

    template<typename T>
    struct MockAllocator {
        using value_type = T;
    
        MockAllocator() = default;
        template<typename U> MockAllocator(const MockAllocator<U>&) noexcept {}
    
        T* allocate(std::size_t) { /*...*/ };
        void deallocate(T*, std::size_t) { /*...*/ };
    };
    
    template<typename T, typename U>
    inline bool operator==(const MockAllocator<T>&, const MockAllocator<U>&) noexcept { return true; };
    
    template<typename T, typename U>
    inline bool operator!=(const MockAllocator<T>&, const MockAllocator<U>&) noexcept { return false; };
    

    and is passed as MockAllocator<char> to the std::basic_string.

    The template parameter is a requirement. An allocator must always be rebindable to other object types, which works automatically for templates of this form. Otherwise a rebind member must be provided.

    The == and != operators are a requirement. They determine whether the two allocators can deallocate memory allocated by the other. They can just return true and false respectively for a simple stateless allocator like this. Since C++20 the != overload is optional.

    For more complex allocators, escpecially stateful ones, other members which are currently defaulted via std::allocator_traits may be required to make it work correctly.

    It is not necessary to inherit from std::allocator. In fact, before C++20, inheriting from std::allocator will cause its rebind to be inherited as well, which will incorrectly rebind to std::allocator</*...*/> instead of your MockAllocator, which violates the allocator requirements. Since C++20 std::allocator doesn't have a rebind, so then your non-template MockAllocator will not be rebindable at all, again violating the requirements.

    The last part is probably why you see the behavior you do. Libstc++ is rebinding the allocator for char internally, which should be completely fine for a proper allocator, but your allocator rebinds to std::allocator<char> instead of MockAllocator, so that it will effectively just use std::allocator<char>.