I frequently need to align the start of a dynamic array to a 16, 32, or 64 Byte boundary for vectorization, e.g., for SSE, AVX, AVX-512. I am looking for a transparent and safe way to use this in conjunction with smart pointers, in particular std::unique_ptr
.
Given an implementation of allocation and deallocation routines, say
template<class T>
T * allocate_aligned(int alignment, int length)
{
// omitted: check minimum alignment, check error
T * raw = 0;
// using posix_memalign as an example, could be made platform dependent...
int error = posix_memalign((void **)&raw, alignment, sizeof(T)*length);
return raw;
}
template<class T>
struct DeleteAligned
{
void operator()(T * data) const
{
free(data);
}
};
I would like to do something like this
std::unique_ptr<float[]> data(allocate_aligned<float>(alignment, length));
but I could not figure out how to do get unique_ptr
to use the proper Deleter
without requiring from the user to specify it (which is a potential cause for errors). The alternative I found was to use a template alias
template<class T>
using aligned_unique_ptr = std::unique_ptr<T[], DeleteAligned<T>>;
Then we can use
aligned_unique_ptr<float> data(allocate_aligned<float>(alignment, length));
The remaining problem is that nothing keeps the user from putting the raw pointer into a std::unique_ptr
.
Apart from that, do you see anything wrong with this? Is there an alternative which is less error prone, but completely transparent to the user after the allocation was done?
You should never return an owning raw pointer. allocate_aligned
violates that. Change it to return the appropriate smart pointer instead:
template<class T>
std::unique_ptr<T[], DeleteAligned<T>> allocate_aligned(int alignment, int length)
{
// omitted: check minimum alignment, check error
T * raw = 0;
// using posix_memalign as an example, could be made platform dependent...
int error = posix_memalign((void **)&raw, alignment, sizeof(T)*length);
return std::unique_ptr<T[], DeleteAligned<T>>{raw};
}
This way, no client can put the raw pointer into an inappropriate smart pointer because they never get the raw pointer in the first place. And you prevent memory leaks from accidentally not putting the raw pointer in a smart one at all.
As @KonradRudolph pointed out, the standard itself is going this way—in C++14, std::make_unique
is exactly such a wrapper for plain new
.
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