HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

Implementation of unique_ptr and make_unique for aligned memory

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
make_uniquealignedformemoryandimplementationunique_ptr

Problem

I am working on a version of std::unique_ptr and std::make_unique for aligned memory. The purpose of this is vectorization, e.g., SSE or AVX, which has higher alignment requirements than the underlying types.

My header providing aligned::unique_ptr and aligned::make_unique (compiles with C++11, no need for C++14 support):

```
#ifndef ALIGNED_H
#define ALIGNED_H

#include
#include

// The "make_unique" parts are copied from GCC (/usr/include/c++/4.9/bits), and
// were adapted for alignment.

namespace aligned {

/// For internal use only!
namespace details {

/// Deleter for single object in aligned memory, used by aligned::unique_ptr
template struct Deleter {
void operator()(T *data) const {
// Single object, was created by placement-new => destruct explicitly
data->~T();
// Data allocated by "posix_memalign", so we must "free" it.
free(data);
}
};

/// Specialization of Deleter for array of objects, used by aligned::unique_ptr
template struct Deleter {
void operator()(T *data) const {
// Data allocated by "posix_memalign", so we must "free" it.
free(data);
}
};

/// Allocation function for aligned memory, used by aligned::make_unique
template
inline typename std::remove_extent::type *alloc(std::size_t num) {
// Ensure minimum alignment for given type
std::size_t align = std::max(std::alignment_of::value, alignment);
// If T is an array type, we remove the "[]"
using TYPE = typename std::remove_extent::type;
TYPE *mem = 0;
int error = posix_memalign((void **)&mem, align, sizeof(TYPE) * num);
if (error == EINVAL)
throw std::logic_error("Error: Alignment must be a power of two "
"(posix_memalign returned EINVAL)");
else if (error != 0)
throw std::bad_alloc();

return mem;
}

/// Default alignment is set to 64 Byte, i.e., the most common cache-line size.
/// This alignment is sufficient at the least for AVX-512.
constexpr std::size_t default_alignment = 64;

} // namespa

Solution

Looks reasonable to me. Good use of standard components to build the thing you want!

TYPE *mem = 0;
int error = posix_memalign((void **)&mem, align, sizeof(TYPE) * num);


I'd have written

void *mem = nullptr;
int error = posix_memalign(&mem, align, sizeof(TYPE) * num);


to get rid of the ugly casts and 0-as-null. (Then return static_cast(mem).)

template  using unique_ptr = std::unique_ptr>;


I have to keep in mind while reading your code that unique_ptr and std::unique_ptr are different types (despite being spelled the same). That's a good design choice for the user of your library, but it's a terrible design choice for the reader of your library. Therefore, IMO you should create a new name for your type in this file, e.g. details::uniq_ptr; and then at the very end of the file you should introduce template using unique_ptr = details::uniq_ptr;. That way you get the best of both worlds: the user gets two things with the same name, and the library reader gets distinct names for distinct concepts.

For make_unique, you'll have to return an aligned::unique_ptr, which must remember the size of the array (or else retrieve it from something like malloc_usable_size or _msize, but those are problematic because they're analogous to vector::capacity instead of vector::size. You don't want to destroy more items than the user provided). Fortunately you can partially specialize your unique_ptr template so that unique_ptr has that extra "size" member. (The STL's unique_ptr is also specialized for T[], but doesn't keep that explicit "size" member because operator delete[] doesn't need it. Instead, it's specialized to provide a different set of accessor operators: [] instead of * and so on.)

// We use constructor with "{}" to prevent narrowing

// Forbidden thanks to "{}" --- did the user want to write
// aligned::make_unique(16)?
// auto x = aligned::make_unique(16);


This is a breaking change / deliberate asymmetry compared to std::unique_ptr. What's wrong with narrowing? Your example implies that you're worried someone might accidentally leave the [] off an array type, but that's pretty far-fetched, isn't it?

Code Snippets

TYPE *mem = 0;
int error = posix_memalign((void **)&mem, align, sizeof(TYPE) * num);
void *mem = nullptr;
int error = posix_memalign(&mem, align, sizeof(TYPE) * num);
template <class T> using unique_ptr = std::unique_ptr<T, details::Deleter<T>>;
// We use constructor with "{}" to prevent narrowing

// Forbidden thanks to "{}" --- did the user want to write
// aligned::make_unique<double[]>(16)?
// auto x = aligned::make_unique<double>(16);

Context

StackExchange Code Review Q#90758, answer score: 2

Revisions (0)

No revisions yet.