I'm playing around with std::function and custom allocators but its not behaving as I expected when I don't provide the function with an initial functor.
When I provide a custom allocator to the constructor but no initial functor, the allocator is never used or so it seems.
This is my code.
//Simple functor class that is big to force allocations
struct Functor128
{
Functor128()
{}
char someBytes[128];
void operator()(int something)
{
cout << "Functor128 Called with value " << something << endl;
}
};
int main(int argc, char* argv[])
{
Allocator<char, 1> myAllocator1;
Allocator<char, 2> myAllocator2;
Allocator<char, 3> myAllocator3;
Functor128 myFunctor;
cout << "setting up function1" << endl;
function<void(int)> myFunction1(allocator_arg, myAllocator1, myFunctor);
myFunction1(7);
cout << "setting up function2" << endl;
function<void(int)> myFunction2(allocator_arg, myAllocator2);
myFunction2 = myFunctor;
myFunction2(9);
cout << "setting up function3" << endl;
function<void(int)> myFunction3(allocator_arg, myAllocator3);
myFunction3 = myFunction1;
myFunction3(19);
}
Output:
setting up function1
Allocator 1 allocating 136 bytes.
Functor128 Called with value 7
setting up function2
Functor128 Called with value 9
setting up function3
Allocator 1 allocating 136 bytes.
Functor128 Called with value 19
So case1: myFunction1 allocates using allocator1 as expected.
case2: myFunction2 is given allocator2 in constructor but when assigned a functor it appears to reset to using the default std::allocator to make the allocation.(hence no print out about allocation).
case3: myFunction3 is given allocator3 in constructor but when assigned to from myFunction1 the allocation takes place using function1's allocator to make the allocation.
Is this correct behaviour? In particular, in case 2 why revert to using default std::allocator? If so what is the point of the empty constructor that takes an allocator as the allocator never gets used.
I am using VS2013 for this code.
My Allocator class is just a minimal implementation that uses new and logs out when it allocates
template<typename T, int id = 1>
class Allocator {
public:
// typedefs
typedef T value_type;
typedef value_type* pointer;
typedef const value_type* const_pointer;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef std::size_t size_type;
typedef std::ptrdiff_t difference_type;
public:
// convert an allocator<T> to allocator<U>
template<typename U>
struct rebind {
typedef Allocator<U> other;
};
public:
inline Allocator() {}
inline ~Allocator() {}
inline Allocator(Allocator const&) {}
template<typename U>
inline Allocator(Allocator<U> const&) {}
// address
inline pointer address(reference r) { return &r; }
inline const_pointer address(const_reference r) { return &r; }
// memory allocation
inline pointer allocate(size_type cnt,
typename std::allocator<void>::const_pointer = 0)
{
size_t numBytes = cnt * sizeof (T);
std::cout << "Allocator " << id << " allocating " << numBytes << " bytes." << std::endl;
return reinterpret_cast<pointer>(::operator new(numBytes));
}
inline void deallocate(pointer p, size_type) {
::operator delete(p);
}
// size
inline size_type max_size() const {
return std::numeric_limits<size_type>::max() / sizeof(T);
}
// construction/destruction
inline void construct(pointer p, const T& t) { new(p)T(t); }
inline void destroy(pointer p) { p->~T(); }
inline bool operator==(Allocator const&) { return true; }
inline bool operator!=(Allocator const& a) { return !operator==(a); }
}; // end of class Allocator
std::function
's allocator support is...weird.
The current spec for operator=(F&& f)
is that it does std::function(std::forward<F>(f)).swap(*this);
. As you can see, this means that memory for f
is allocated using whatever std::function
uses by default, rather than the allocator used to construct *this
. So the behavior you observe is correct, though surprising.
Moreover, since the (allocator_arg_t, Allocator)
and (allocator_arg_t, Allocator, nullptr_t)
constructors are noexcept
, they can't really store the allocator even if they wanted to (type-erasing an allocator may require a dynamic allocation). As is, they are basically no-ops that exist to support the uses-allocator construction protocol.
LWG very recently rejected an issue that would change this behavior.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With