Reputation: 53960
I recently had some interest for std::allocator
, thinking it might solve an issue I had with some design decision on C++ code.
Now I've read some documentation about it, watched some videos, like Andrei Alexandrescu's one at CppCon 2015, and I now basically understand I shouldn't use them, because they're not designed to work the way I think allocators might work.
That being said, before realising this, I write some test code to see how a custom subclass of std::allocator
could work.
Obviously, didn't work as expected... : )
So the question is not about how allocators should be used in C++, but I'm just curious to learn exactly why my test code (provided below) is not working.
Not because I want to use custom allocators. Just curious to see the exact reason...
typedef std::basic_string< char, std::char_traits< char >, TestAllocator< char > > TestString;
int main( void )
{
TestString s1( "hello" );
TestString s2( s1 );
s1 += ", world";
std::vector< int, TestAllocator< int > > v;
v.push_back( 42 );
return 0;
}
Complete code for TestAllocator
is provided at the end of this question.
Here I'm simply using my custom allocator with some std::basic_string
, and with std::vector
.
With std::basic_string
, I can see an instance of my allocator is actually created, but no method is called...
So it just looks like it's not used at all.
But with std::vector
, my own allocate
method is actually being called.
So why is there a difference here?
I did try with different compilers and C++ versions.
Looks like the old GCC versions, with C++98, do call allocate
on my TestString
type, but not the new ones with C++11 and later.
Clang also don't call allocate
.
So just curious to see an explanation about these different behaviours.
Allocator code:
template< typename _T_ >
struct TestAllocator
{
public:
typedef _T_ value_type;
typedef _T_ * pointer;
typedef const _T_ * const_pointer;
typedef _T_ & reference;
typedef const _T_ & const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
typedef std::true_type propagate_on_container_move_assignment;
typedef std::true_type is_always_equal;
template< class _U_ >
struct rebind
{
typedef TestAllocator< _U_ > other;
};
TestAllocator( void ) noexcept
{
std::cout << "CTOR" << std::endl;
}
TestAllocator( const TestAllocator & other ) noexcept
{
( void )other;
std::cout << "CCTOR" << std::endl;
}
template< class _U_ >
TestAllocator( const TestAllocator< _U_ > & other ) noexcept
{
( void )other;
std::cout << "CCTOR" << std::endl;
}
~TestAllocator( void )
{
std::cout << "DTOR" << std::endl;
}
pointer address( reference x ) const noexcept
{
return std::addressof( x );
}
pointer allocate( size_type n, std::allocator< void >::const_pointer hint = 0 )
{
pointer p;
( void )hint;
std::cout << "allocate" << std::endl;
p = new _T_[ n ]();
if( p == nullptr )
{
throw std::bad_alloc() ;
}
return p;
}
void deallocate( _T_ * p, std::size_t n )
{
( void )n;
std::cout << "deallocate" << std::endl;
delete[] p;
}
const_pointer address( const_reference x ) const noexcept
{
return std::addressof( x );
}
size_type max_size() const noexcept
{
return size_type( ~0 ) / sizeof( _T_ );
}
void construct( pointer p, const_reference val )
{
( void )p;
( void )val;
std::cout << "construct" << std::endl;
}
void destroy( pointer p )
{
( void )p;
std::cout << "destroy" << std::endl;
}
};
template< class _T1_, class _T2_ >
bool operator ==( const TestAllocator< _T1_ > & lhs, const TestAllocator< _T2_ > & rhs ) noexcept
{
( void )lhs;
( void )rhs;
return true;
}
template< class _T1_, class _T2_ >
bool operator !=( const TestAllocator< _T1_ > & lhs, const TestAllocator< _T2_ > & rhs ) noexcept
{
( void )lhs;
( void )rhs;
return false;
}
Upvotes: 1
Views: 174
Reputation: 93284
std::basic_string
can be implemented using the small buffer optimization (a.k.a. SBO or SSO in the context of strings) - this means that it internally stores a small buffer that avoids allocations for small strings. This is very likely the reason your allocator is not being used.
Try changing "hello"
to a longer string (more than 32 characters) and it will probably invoke allocate
.
Also note that the C++11 standard forbids std::string
to be implemented in a COW (copy-on-write) fashion - more information in this question: "Legality of COW std::string implementation in C++11"
The Standard forbids std::vector
to make use of the small buffer optimization: more information can be found in this question: "May std::vector
make use of small buffer optimization?".
Upvotes: 5