简体   繁体   中英

Rambling on std::allocator

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;
}

std::basic_string can be implemented using the small buffer optimization (aka 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?" .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM