Рамблинг на 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 использовать небольшую буферную оптимизацию?

Другие вопросы по тегам