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?
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>
.