Reputation: 75649
When I call posix_memalign
to allocate aligned memory for an object of type Foo
in my C++ code, I am required to do a reinterpret_cast
of the address of that pointer to void**
.
In general when I encounter this situation, it implies that I am missing some language feature. That is, it feels like I am calling malloc
in c++
when I should be calling new
.
,
Is there a type-aware new
equivalent for aligned memory allocation in c++
?
Upvotes: 6
Views: 4719
Reputation: 179717
C++11 has added native language support for alignment declarations and aligned allocation.
You can specify alignas(N)
on your type in C++11 to specify the minimum alignment for new objects, which the default new
will respect.
Example from cppreference:
struct alignas(16) sse_t { // SSE-safe struct aligned on 16-byte boundaries
float v[4];
};
then you can simply do
sse_t *ssevec = new sse_t;
For a replacement for posix_memalign
, you can use std::aligned_storage<sizeof(T), N>
, also in C++11.
Upvotes: 1
Reputation: 275966
I will start with the core advice first.
Foo* aligned_foo() {
void* raw = 0;
if(posix_memalign(&raw, 8, sizeof(Foo)))
return 0; // we could throw or somehow communicate the failure instead
try{
return new(raw) Foo();
}catch(...){
free(raw);
throw;
}
}
then when you are done with the Foo* foo
, do a foo->~Foo(); free(foo);
instead of delete
.
Note the lack of reinterpret_cast
s.
Here is an attempt to make it generic:
// note: stateless. Deleting a derived with a base without virtual ~base a bad idea:
template<class T>
struct free_then_delete {
void operator()(T*t)const{
if(!t)return;
t->~T();
free(t);
};
};
template<class T>
using aligned_ptr=std::unique_ptr<T,free_then_delete<T>>;
// the raw version. Dangerous, because the `T*` requires special deletion:
template<class T,class...Args>
T* make_aligned_raw_ptr(size_t alignment, Args&&...args) {
void* raw = 0;
if(int err = posix_memalign(&raw, alignment, sizeof(T)))
{
if (err==ENOMEM)
throw std::bad_alloc{};
return 0; // other possibility is bad alignment: not an exception, just an error
}
try {
// returns a T*
return new(raw) T(std::forward<Args>(args)...);
} catch(...) { // the constructor threw, so clean up the memory:
free(raw);
throw;
}
}
template<class T,class...Args> // ,class... Args optional
aligned_ptr<T> make_aligned_ptr(size_t alignment=8, Args&&...args){
T* t = make_aligned_raw_ptr<T>(alignment, std::forward<Args>(args)...);
if (t)
return aligned_ptr<T>(t);
else
return nullptr;
}
The unique_ptr
alias aligned_ptr
bundles the destroyer along with the pointer -- as this data requires destruction and free, not delete, this makes it clear. You can still .release()
the pointer out, but you still have to do the steps.
Upvotes: 5
Reputation: 2234
Actually, you don't want to do a reinterpret_cast
because then your Foo
constructor isn't called. If you need to allocate memory from a special place, you then call placement new to construct the object in that memory:
void* alloc;
posix_memalign(&alloc, 8, sizeof(Foo));
Foo* foo = new (foo) Foo();
The only other way (pre C++11) would be overriding the new
operator for your class. That works if you have a particular class that always requires this special allocation:
class Foo {
void* operator new(size_t size) {
void* newobj;
posix_memalign(&newobj, 8, sizeof(Foo));
return newobj;
}
};
Then anytime you call new Foo()
it will invoke this allocator. See http://en.cppreference.com/w/cpp/memory/new/operator_new for more information. Overriding operator new
and operator delete
can be done for individual classes or globally.
Upvotes: 3