clarification of specifics of P0137
std::unique_ptr<Foo, destroy1>(reinterpret_cast<Foo*>(p.release()), destroy1());
This doesn't work, because you're using the wrong pointer.
p.release() thinks it points to an
unsigned char. However, that's not the object you want to point to. What you want to point to is the object that lives inside this array, the
Foo you've created.
So you are now subject to [basic.life]/8. The gist of that is that you can only use the previous pointer as a pointer to the new object if they are of the same type. Which they're not in your case.
Now, I could tell you to
launder the pointer, but the more reasonable way to handle this is to just store the pointer returned by the placement-new call:
auto p = std::make_unique<unsigned char>(sizeof(Foo)); auto ret = std::unique_ptr<Foo, destroy1>(new(p.get()) Foo(), destroy1()); p.release(); return ret;
That pointer will always be correct.
Your use of of placement-new is not optional. [intro.object]/1 tells us:
An object is created by a definition (3.1), by a new-expression (5.3.4), when implicitly changing the active member of a union (9.3), or when a temporary object is created (4.4, 12.2).
When you allocate an
unsigned char, that's the object you have created in that storage. You cannot simply pretend that it is a
Foo, just because
Foo is an aggregate. [intro.object]/1 doesn't allow that. You must explicitly create that object via one of the mechanisms listed above. Since you can't use a definition,
union member activation, or temporary objects with arbitrary memory buffers to create objects from existing storage, the only recourse you have to create objects is a new-expression.
delete1, you do need a custom deleter, since the default deleter will call
delete on the
Foo pointer. Your code is as follows:
auto memory = std::unique_ptr<unsigned char>(reinterpret_cast<unsigned char*>(p)); p->~Foo();
unsigned char has some special logic to it, in terms of how it behaves when objects are allocated in their storage, thanks to [intro.object]/3-4. If the object entirely overlays the storage of the
unsigned char, then it functions as if the object were allocated within the array. That means that the
unsigned char is still technically there; it has not destroy the byte array.
As such, you can still delete the byte array, which your code here does.
This is also wrong, due to further violations of [basic.life]/8. A fixed version would be similar to the above:
auto p = malloc_ptr(reinterpret_cast<unsigned char*>(std::malloc(sizeof(Foo)))); auto ret std::unique_ptr<Foo, destroy2>(new(p.get()) Foo(), destroy2()); p.release(); return ret;
malloc never creates an object via [intro.object]/1; it only acquires storage. As such, placement-new is again required.
free just releases memory; it doesn't deal with objects. So your
delete2 is essentially fine (though the use of
malloc_ptr there makes it needlessly confusing).
This has the same [basic.life]/8 problems that the rest of your examples have:
alignas(Foo) static unsigned char storage[sizeof(Foo)]; static auto pCandidate = std::shared_ptr<Foo>(new(storage) Foo(), nodelete()); return pCandidate;
But other than that, it's fine (so long as you don't break it elsewhere). Why? That's complex.
[basic.start.term]/1 tells us that static objects are destroyed in the reverse order of their initialization. And [stmt.decl]/4 tells us that block-scoped static objects are initialized in the order they are encountered in a function.
Therefore, we know that
pCandidate will be destroyed before
storage. So long as you don't keep a copy of that
shared_ptr in a static variable, or otherwise fail to destroy/reset all such shared objects before termination, you should be fine.
That all being said, using blocks of
unsigned char is really pre-C++11. We have
std::aligned_union now. Use them.