One criticism of Boost shared_array is the lack of make_shared_array utility which ensures only a single allocation. A second criticism is Boost shared_array does not support custom allocators and so also lacks an allocate_shared_array utility. The following code is my initial implementation of an allocate_array that yields a shared_ptr of an array created with the given allocator in a single allocation.
/*
* Copyright (c) 2012 All Rights Reserved, Glen Joseph Fernandes
*/
#ifndef glenfern_allocate_array_h
#define glenfern_allocate_array_h
#include <boost/shared_ptr.hpp>
#include <glenfern/detail/allocate_helper.h>
#include <glenfern/detail/array_deleter.h>
namespace glenfern {
template<typename T, typename A>
inline boost::shared_ptr<T> allocate_array(const A& allocator, std::size_t size) {
T* p1 = 0;
glenfern::detail::allocate_helper<A, T> a1(allocator, size, &p1);
glenfern::detail::array_deleter<T> d1(size);
boost::shared_ptr<T> s1(p1, d1, a1);
glenfern::detail::array_deleter<T>* d2;
d2 = boost::get_deleter<glenfern::detail::array_deleter<T> >(s1);
d2->construct(p1);
return boost::shared_ptr<T>(s1, p1);
}
}
#endif
The magic is in the helper allocator class; pay attention to the allocate method.
/*
* Copyright (c) 2012 All Rights Reserved, Glen Joseph Fernandes
*/
#ifndef glenfern_detail_allocate_helper_h
#define glenfern_detail_allocate_helper_h
#include <boost/type_traits/alignment_of.hpp>
namespace glenfern {
namespace detail {
template<typename A, typename T, typename Y = T>
class allocate_helper {
template<typename A9, typename T9, typename Y9>
friend class allocate_helper;
typedef typename A::template rebind<Y> ::other A2;
typedef typename A::template rebind<char>::other A3;
public:
typedef typename A2::value_type value_type;
typedef typename A2::pointer pointer;
typedef typename A2::const_pointer const_pointer;
typedef typename A2::reference reference;
typedef typename A2::const_reference const_reference;
typedef typename A2::size_type size_type;
typedef typename A2::difference_type difference_type;
template<typename U>
struct rebind {
typedef allocate_helper<A, T, U> other;
};
allocate_helper(const A& allocator, std::size_t size, T** data)
: allocator(allocator),
size(sizeof(T) * size),
data(data) {
}
template<class U>
allocate_helper(const allocate_helper<A, T, U>& other)
: allocator(other.allocator),
size(other.size),
data(other.data) {
}
pointer address(reference value) const {
return allocator.address(value);
}
const_pointer address(const_reference value) const {
return allocator.address(value);
}
size_type max_size() const {
return allocator.max_size();
}
pointer allocate(size_type count, const void* value = 0) {
std::size_t a1 = boost::alignment_of<T>::value;
std::size_t n1 = count * sizeof(Y) + a1 - 1;
char* p1 = A3(allocator).allocate(n1 + size, value);
char* p2 = p1 + n1;
while (std::size_t(p2) % a1 != 0) {
p2--;
}
*data = reinterpret_cast<T*>(p2);
return reinterpret_cast<Y*>(p1);
}
void deallocate(pointer memory, size_type count) {
std::size_t a1 = boost::alignment_of<T>::value;
std::size_t n1 = count * sizeof(Y) + a1 - 1;
char* p1 = reinterpret_cast<char*>(memory);
A3(allocator).deallocate(p1, n1 + size);
}
void construct(pointer memory, const Y& value) {
allocator.construct(memory, value);
}
void destroy(pointer memory) {
allocator.destroy(memory);
}
template<typename U>
bool operator==(const allocate_helper<A, T, U>& other) const {
return allocator == other.allocator;
}
template<typename U>
bool operator!=(const allocate_helper<A, T, U>& other) const {
return !(*this == other);
}
private:
A2 allocator;
std::size_t size;
T** data;
};
}
}
#endif
The next piece of code required is the array deleter and initializer class.
/*
* Copyright (c) 2012 All Rights Reserved, Glen Joseph Fernandes
*/
#ifndef glenfern_detail_array_deleter_h
#define glenfern_detail_array_deleter_h
#include <glenfern/detail/array_utility.hpp>
namespace glenfern {
namespace detail {
template<typename T>
class array_deleter {
public:
array_deleter(std::size_t size)
: size(size),
object(0) {
}
~array_deleter() {
if (object) {
array_destroy(object, size);
}
}
void construct(T* memory) {
array_construct(memory, size);
object = memory;
}
void operator()(T*) {
if (object) {
array_destroy(object, size);
object = 0;
}
}
private:
std::size_t size;
T* object;
};
}
}
#endif
The final piece of code is the array construction and destruction utilities.
/*
* Copyright (c) 2012 All Rights Reserved, Glen Joseph Fernandes
*/
#ifndef glenfern_detail_array_utility_h
#define glenfern_detail_array_utility_h
#include <boost/type_traits/has_trivial_constructor.hpp>
#include <boost/type_traits/has_trivial_destructor.hpp>
namespace glenfern {
namespace detail {
template<typename T>
inline void array_destroy(T*, std::size_t, boost::true_type) {
// do nothing
}
template<typename T>
inline void array_destroy(T* memory, std::size_t size, boost::false_type) {
for (std::size_t i = size; i > 0; ) {
object[--i].~T();
}
}
template<typename T>
inline void array_destroy(T* memory, std::size_t size) {
boost::has_trivial_destructor<T> type;
array_destroy(memory, size, type);
}
template<typename T>
inline void array_construct(T*, std::size_t, boost::true_type) {
// do nothing
}
template<typename T>
inline void array_construct(T* memory, std::size_t size, boost::false_type) {
std::size_t i = 0;
try {
for (; i < size; i++) {
void* p1 = memory + i;
::new(p1) T;
}
} catch (...) {
array_destroy(memory, i);
throw;
}
}
template<typename T>
inline void array_construct(T* memory, std::size_t size) {
boost::has_trivial_default_constructor<T> type;
array_construct(memory, size, type);
}
}
}
#endif
The trick is providing an allocator wrapper which allocates space for the desired array while performing the requested allocation by shared_ptr and preserves the start address of that array space. The above is valid ISO/IEC 14882:2003 C++ (and C++11) and the implementation of a corresponding make_array is even simpler. A more recent copy of my source code can be found at any of the following locations: