Рамблинг на std::allocator
У меня недавно был некоторый интерес к std::allocator
Я думаю, что это может решить проблему, которая у меня возникла с каким-то дизайнерским решением для кода C++
Теперь я прочитал некоторую документацию об этом, посмотрел несколько видео, например, видео Андрея Александреску на CppCon 2015, и теперь я в основном понимаю, что не должен их использовать, потому что они не предназначены для работы так, как я думаю, могут работать распределители.
При этом, прежде чем понять это, я пишу тестовый код, чтобы увидеть, как пользовательский подкласс std::allocator
может работать
Очевидно, не сработало, как ожидалось...:)
Так что вопрос не в том, как распределители должны использоваться в C++, но мне просто интересно узнать, почему мой тестовый код (приведенный ниже) не работает.
Не потому, что я хочу использовать пользовательские распределители. Просто любопытно узнать точную причину...
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;
}
Полный код для TestAllocator
предоставляется в конце этого вопроса.
Здесь я просто использую свой собственный распределитель с некоторыми std::basic_string
, и с std::vector
,
С std::basic_string
Я вижу, что экземпляр моего распределителя фактически создан, но метод не вызывается...
Так что, похоже, он вообще не используется.
Но с std::vector
, мой собственный allocate
метод на самом деле вызывается.
Так почему здесь есть разница?
Я попробовал с разными компиляторами и версиями C++. Похоже, старые версии GCC, с C++98, сделать вызов allocate
на моем TestString
типа, но не новые с C++11 и выше.
Clang также не звоните allocate
,
Так что просто любопытно увидеть объяснение об этом различном поведении.
Код распределителя:
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;
}
1 ответ
std::basic_string
может быть реализован с использованием оптимизации небольшого буфера (иначе SBO или SSO в контексте строк) - это означает, что он внутренне хранит небольшой буфер, избегая выделения для маленьких строк. Это очень вероятно причина, по которой ваш распределитель не используется.
Попробуйте изменить "hello"
в более длинную строку (более 32 символов), и это, вероятно, вызовет allocate
,
Также обратите внимание, что стандарт C++11 запрещает std::string
должен быть реализован в режиме COW (копирование при записи) - более подробная информация в этом вопросе: "Легальность реализации COW std::string в C++11"
Стандарт запрещает std::vector
использовать оптимизацию малого буфера: больше информации можно найти в этом вопросе: std::vector
использовать небольшую буферную оптимизацию?