Search code examples
c++memory-managementallocator

How do I make a class pass through scoped allocators?


I am trying to make an allocator aware class that can be used in a vector with a custom allocator. The class has a std::string as a member and I want this string to be allocated with the allocator like the class is.

How do I make such an allocator aware class? The example below allocates the class ClassA on the arena, but it allocates the ClassA:mVarC on the default Allocator.

using XAlloc1 = Allocator<char>;
using xstring = basic_string< char, char_traits<char>, XAlloc1 >;

template< typename Alloc = std::allocator<xstring> >
class ClassA : public Alloc {
public:
    ClassA( xstring & str, Alloc & alloc )
        : mVarA( int() ), mVarB( double() ), mVarC( str, alloc ) {
    }

    ClassA( xstring & str )
        : mVarA( int() ), mVarB( double() ), mVarC( str ) {
    }

    int mVarA;
    double mVarB;
    xstring mVarC;
};

using XAlloc2 = scoped_allocator_adaptor< Allocator<xstring>,
                                          Allocator<char> >;
using XClassA = ClassA< XAlloc2 >;

using XAlloc3 = scoped_allocator_adaptor< Allocator<XClassA>,
                                          Allocator<xstring>,
                                          Allocator<char> >;
using xvec = vector< XClassA, XAlloc3 > ;

void foo() {
    MemoryPool<145> arena();
    XAlloc3 alloc( arena );
    xvec v( alloc );
    v.emplace_back( XClassA( xstring( "Another very long text" ) ) );
}

Solution

  • In order to make a class 'allocator aware' you must

    • include 'using allocator_type = Alloc;' so that allocators can query for it.
    • write allocator versions of

      • plain constructor. ClassA( const xstring & str, const Alloc & alloc )
      • copy constructor. ClassA( const ClassA & o, const Alloc & alloc )
      • move constructor. ClassA( ClassA && o, const Alloc & alloc )

      The constructors must have a 'const Alloc &' parameter as the trailing parameter, unless you specify that the class uses leading allocator parameter.

      • plain constructor. ClassA( allocator_arg_t, const Alloc & alloc, const xstring & str )
      • copy constructor. ClassA( allocator_arg_t, const Alloc & alloc, const ClassA & o )
      • move constructor. ClassA( allocator_arg_t, const Alloc & alloc, ClassA && o )

      allocator_arg_t is not used. It is an empty struct. (I don't know why it must be there. It just must.

    .

    template< typename Alloc = std::allocator<xstring> >
    class ClassA {
    public:
        using allocator_type = Alloc;
    
        ClassA( const xstring & str, const Alloc & alloc ) 
            : mVarA( int() ), mVarB( double() ), mVarC( str, alloc ) { }
        ClassA( const xstring & str ) 
            : mVarA( int() ), mVarB( double() ), mVarC( str ) { }
        ClassA( const ClassA & o, const Alloc & alloc ) 
            : mVarA( o.mVarA ), mVarB( o.mVarB ), mVarC( o.mVarC, alloc ) { }
        ClassA( const ClassA & o ) 
            : VarA( o.mVarA ), mVarB( o.mVarB ), mVarC( o.mVarC ) { }
        ClassA( ClassA && o, const Alloc & alloc ) 
            : mVarA( move( o.mVarA ) ), mVarB( move( o.mVarB ) ), mVarC( move( o.mVarC ), alloc ) { }
        ClassA( ClassA && o ) 
            : mVarA( move( o.mVarA ) ), mVarB( move( o.mVarB ) ), mVarC( move( o.mVarC ) ) { }
    
        int mVarA;
        double mVarB;
        xstring mVarC;
    };
    
    // // You can specify an allocator for each of the nested containers.
    //using XAlloc2 = scoped_allocator_adaptor< Allocator<xstring>,
    //                                          Allocator<char> >;
    //using XClassA = ClassA< XAlloc2 >;
    //
    //using XAlloc3 = scoped_allocator_adaptor< Allocator<XClassA>,
    //                                          Allocator<xstring>,
    //                                          Allocator<char> >;
    //using xvec = vector< XClassA, XAlloc3 > ;
    
    // Or you can just specify one allocator. This allocator will then be reused for all nested containers.
    using XAlloc2 = scoped_allocator_adaptor< Allocator<xstring> >;
    using XClassA = ClassA< XAlloc2 >;
    using xvec = vector< XClassA, XAlloc2 >;
    
    void funk() {
        MemoryPool<145> arena();
        XAlloc2 alloc( arena );
        xvec v( alloc );
        v.push_back( XClassA( xstring( "Another very long text" ) ) );
    }