3

In the code below is the function make_vector(). It creates a vector and returns it to the caller. I want to be able to specify an allocator for the vector to use, but use the default std::allocator by default. This is because under some circumstances the default allocator is all I need, but other times I need to allocate from some pre-defined memory pools.

The closest I've come is the make_vector2() function template. It works with the std::allocator, but I don't know how to pass the 'arena' argument into my custom allocator.

Hopefull this working c++11 example will explain it better:

#include <malloc.h>
#include <cinttypes>
#include <cstddef>
#include <iostream>
#include <limits>
#include <stdexcept>
#include <vector>

namespace mem
{
    // Memory arena to allocate from.
    enum class ARENA
    {
        TEXTURES,
        FONTS,
        SCRIPTS
    };

    // Allocate block from specific arena.
    void *malloc( const std::size_t size, const ARENA arena )
    {
        return std::malloc( size /*, arena */ );
    }

    // Free block from specific arena.
    void free( void *ptr, const ARENA arena )
    {
        std::free( ptr /*, arena */ );
    }


    // The allocator - forward declaration.
    // Not derived from std::allocator - should it be?
    // Based on code from here:
    // http://drdobbs.com/184403759?pgno=2
    template<typename T> class allocator;

    // Specialised for void.
    template<> class allocator<void>
    {
        public:
            typedef std::size_t size_type;
            typedef ptrdiff_t difference_type;
            typedef void* pointer;
            typedef const void* const_pointer;
            typedef void value_type;

            template<typename U> struct rebind
            {
                typedef allocator<U> other;
            };
    };


    template<typename T> class allocator
    {
        public:
            typedef std::size_t size_type;
            typedef std::ptrdiff_t difference_type;
            typedef T* pointer;
            typedef const T* const_pointer;
            typedef T& reference;
            typedef const T& const_reference;
            typedef T value_type;

            template<typename U> struct rebind
            {
                 typedef allocator<U> other;
            };

            allocator( ARENA arena ) noexcept :
                arena_( arena )
            {}

            ~allocator() noexcept
            {}

            pointer address( reference x ) const
            {
                    return &x;
            }

            const_pointer address( const_reference x ) const
            {
                return &x;
            }

            pointer allocate( size_type n, allocator<void>::const_pointer hint = 0 )
            {
                void *p = mem::malloc( n * sizeof( T ), arena_ );
                if ( p == nullptr )
                {
                        throw std::bad_alloc();
                }
                return static_cast<pointer>( p );
            }

            void deallocate( pointer p, size_type n )
            {
                mem::free( p, arena_ );
            }

            size_type max_size() const noexcept
            {
                return std::numeric_limits<std::size_t>::max() / sizeof( T );
            }

            void construct( pointer p, const T& val )
            {
                new (p) T(val);
            }

            void destroy( pointer p )
            {
                p->~T();
            }

            allocator( const allocator& src ) noexcept
            {
                arena_ = src.arena_;
            }

            ARENA arena_;
    };
} // namespace mem

template<class T1, class T2> bool operator==( const mem::allocator<T1> &alloc1, const mem::allocator<T2> &alloc2 ) noexcept
{
    return alloc1.arena_ == alloc2.arena_;
}

template<class T1, class T2> bool operator!=( const mem::allocator<T1> &alloc1, const mem::allocator<T2> &alloc2 ) noexcept
{
    if alloc1.arena_ != alloc2.arena_;
}

// How do I allow the custom allocator to be passed? Function parameter? Template?
std::vector<uint8_t> make_vector()
{
    std::vector<uint8_t> vec;
    // Do stuff with the vector
    return vec;
}

// This template function seems to work with std::allocator
template< typename T > std::vector<uint8_t,T> make_vector2()
{
    std::vector<uint8_t,T> vec;
    // Do stuff with the vector.
    return vec;
}

int main( int argc, char **argv )
{
    // vec1 - Allocates from TEXTURES arena
    // See the C++11 FAQ by  Bjarne Stroustrup here:
    // http://www2.research.att.com/~bs/C++0xFAQ.html#scoped-allocator
    std::vector<uint8_t, mem::allocator<uint8_t>> vec1( mem::allocator<uint8_t>{mem::ARENA::TEXTURES} );

    // vec2 - Make the vector using the default allocator.
    auto vec2 = make_vector2< std::allocator<uint8_t> >();

    return 0;
}

In main() vec1 is created to use the TEXTURES arena for allocation. The arena to use is passed into the constructor of the allocator. Vec2 is created by the make_vector2() templated function and uses the std::allocator.

Q: How can I define the make_vector() function so it can create a vector that uses the std::allocator or the custom pool allocator above?

3 Answers 3

6

In C++11, function templates can have default template arguments:

template<class T, class Alloc = std::allocator<T>>
std::vector<T, Alloc> make_vector(Alloc const& al = Alloc()){
  std::vector<T, Alloc> v(al);
  // ...
  return v;
}

Live example on Ideone.

In C++03 (or with compilers that don't support that feature), it's a bit more cumbersome, but you can overload based on the template parameters:

template<class T>
std::vector<T> make_vector(){
  std::vector<T> v;
  // ...
  return v;
}

template<class T, class Alloc>
std::vector<T, Alloc> make_vector(Alloc const& al = Alloc()){
  std::vector<T, Alloc> v(al);
  // ...
  return v;
}

Live example on Ideone.

Sign up to request clarification or add additional context in comments.

6 Comments

What is the point of the 2nd make_vector function for c++03 example?
The constructor for the custom mem::allocator class requires a mem::ARENA value as a parameter, as shown by vec1 in main(). How do I pass that into the templated function?
@DrTwox: Just.. pass it? If the allocator is implicitly constructible from the mem::ARENA argument, you can just call make_vector<uint8_t, mem::allocator<uint8_t>>(mem::ARENA::textures). If it's not implicitly constructible, well, create the allocator and pass that to the function, just like your vec1 example.
Allocators have to be copyable.
@Xeo: Thank you for pointing out what should have been obvious to me! I got caught up thinking I needed to use an initializer list.
|
3

You can provide two different function overloads:

template<typename T, typename Alloc>
std::vector<T, Alloc> func(Alloc a)
{
    return std::vector<T, Alloc>();
}

template<typename T>
std::vector<T, std::allocator<T> > func()
{
    return func<T>(std::allocator<T>());
}

Comments

0

The problem is that the type of allocator is part of the type of the vector, so you are basically asking for a function whose return value type depends on an argument. You can do this by returning a boost::variant, but I'm not sure this is a good idea. The client code still needs to know the type in order to use the vector.

3 Comments

No, they don't, auto relieves them of this.
@Xeo Unless I've badly misunderstood something, auto is a compile time feature; what the client needs is a runtime switch on the actual type.
Well, in the example code, the OP provides the wanted allocator as a template argument, as such at compile time. Unless I misunderstood the question, the OP just wants to be able to specify the allocator for the vector, but fall back on the default allocator if it's not specified.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.