
I have a couple of questions about combining smart pointers and STL allocators. First of all, am I right in assuming that I can use the shared_* templates right out of the box, as long as I define a custom deleter that uses the allocator's deallocate function? But if that's so, how exactly is it done? Part of my confusion comes from the fact that the STL allocator documentation I've come across isn't exactly clear on the semantics of allocators (I don't have a copy of the standard). For instance, I'm not 100% sure whether or not I have to call construct() after allocate() and destroy() before deallocate(). This is what I have so far: class allocator_helper { public: allocator_helper(allocator_type& a) : a_(a) {} void operator()(void const* p) const { a_.deallocate(static_cast<allocator_type::pointer>(p)); } private: allocator_type& a_; }; foo* bar = static_cast<foo*>(get_allocator().allocate(1)); shared_ptr<foo> p_foo(bar, allocator_helper(get_allocator())); foo* baz = static_cast<foo*>(get_allocator().allocate(10)); shared_array<foo> p_foo(baz, allocator_helper(get_allocator())); Now, for scoped_* I would probably have to rewrite them to use the allocator, right? Or is there a way to use allocators with the existing scoped_* templates? Mark

Mark A. Gibbs wrote:
I have a couple of questions about combining smart pointers and STL allocators.
First of all, am I right in assuming that I can use the shared_* templates right out of the box, as long as I define a custom deleter that uses the allocator's deallocate function?
But if that's so, how exactly is it done? Part of my confusion comes from the fact that the STL allocator documentation I've come across isn't exactly clear on the semantics of allocators (I don't have a copy of the standard). For instance, I'm not 100% sure whether or not I have to call construct() after allocate() and destroy() before deallocate().
Yes, you have to. allocate() gives you raw memory with the correct size and alignment, but there is no object in it. To create the object in that memory, you need to construct() it. Similarly, destroy() invokes the destructor and deallocate() only, well, deallocates.
This is what I have so far:
class allocator_helper { public: allocator_helper(allocator_type& a) : a_(a) {}
void operator()(void const* p) const { a_.deallocate(static_cast<allocator_type::pointer>(p)); }
This should probably be void operator()(allocator_type::pointer p) const { a_.destroy(p); a_.deallocate(p, 1); }
private: allocator_type& a_; };
foo* bar = static_cast<foo*>(get_allocator().allocate(1));
new(bar) foo(args); or get_allocator().construct(bar, foo(args));
shared_ptr<foo> p_foo(bar, allocator_helper(get_allocator()));
foo* baz = static_cast<foo*>(get_allocator().allocate(10)); shared_array<foo> p_foo(baz, allocator_helper(get_allocator()));
Now, for scoped_* I would probably have to rewrite them to use the allocator, right? Or is there a way to use allocators with the existing scoped_* templates?
No, scoped_* do not support allocators. Since they match raw pointers in size, they can keep no additional state such as an instance of, or a reference to, an allocator.

Peter Dimov wrote:
copy of the standard). For instance, I'm not 100% sure whether or not I have to call construct() after allocate() and destroy() before deallocate().
Yes, you have to. allocate() gives you raw memory with the correct size and alignment, but there is no object in it. To create the object in that memory, you need to construct() it. Similarly, destroy() invokes the destructor and deallocate() only, well, deallocates.
Excellent. That's one big headache I avoid. but:
foo* bar = static_cast<foo*>(get_allocator().allocate(1));
new(bar) foo(args);
or
get_allocator().construct(bar, foo(args));
are these two expressions identical? I mean for any allocator (not just std::allocator<T>). The former looks more efficient but less general than the latter.
No, scoped_* do not support allocators. Since they match raw pointers in size, they can keep no additional state such as an instance of, or a reference to, an allocator.
Ok, so that means i have to write my own scoped_allocator_ptr<T, Allocator> I guess. No biggie, at least I don't have to rewrite shared_ptr. Mark

Mark A. Gibbs wrote:
Peter Dimov wrote:
foo* bar = static_cast<foo*>(get_allocator().allocate(1));
new(bar) foo(args);
or
get_allocator().construct(bar, foo(args));
are these two expressions identical? I mean for any allocator (not just std::allocator<T>). The former looks more efficient but less general than the latter.
The short answer is that nobody knows, as allocators are underspecified (standardese for "a mess"). The standard says, in Table 32 - Allocator requirements, that a.construct(p, t) must have the effect new((void*)p) T(t). In addition, there is no guarantee that the standard library containers will bother to call construct() and not use placement new directly. So the choice which form to use is up to you. ;-)
participants (2)
-
Mark A. Gibbs
-
Peter Dimov