On create1
std::unique_ptr<Foo, destroy1>(reinterpret_cast<Foo*>(p.release()), destroy1());
This does not work because you are using the wrong pointer.
p.release() thinks it points to an unsigned char[] . However, this is not the object you want to point to. What you want to specify is the object that lives inside this array, Foo , which you created.
So now you obey [basic.life] / 8. The bottom line is that you can use the previous pointer only as a pointer to a new object, if they are of the same type. That they are not in your case.
Now I can tell you the launder pointer, but a more reasonable way to handle this is to simply save the pointer returned by the 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;
This pointer will always be correct.
Your use of the new placement is optional. [intro.object] / 1 tells us:
An object is created by definition (3.1), a new expression (5.3.4), when an active member of an association (9.3) is implicitly changed, or when a temporary object is created (4.4, 12.2).
When you allocate unsigned char[] , then the object that you created in this repository. You can't just pretend it's Foo , simply because Foo is an aggregate. [intro.object] / 1 does not allow this. You must explicitly create this object using one of the mechanisms listed above. Since you cannot use a definition, union member activation, or temporary objects with arbitrary memory buffers to create objects from an existing repository, the only way you must create is a new expression.
In particular, the placement is new.
As for delete1 , you will need a custom div, since default will cause delete on the Foo pointer to default. 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 for it, in terms of how it behaves when placing objects in their storage, thanks to [intro.object] / 3-4. If the object completely overlaps the unsigned char[] storage, it functions as if the object was allocated inside the array. This means that unsigned char[] still technically exists; it does not destroy the byte array.
So you can still remove the byte array that your code does here.
On create2
This is also incorrect due to further violations of [basic.life] / 8. The fixed version will be similar to the one 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;
Unlike new expressions, malloc never creates an object through [intro.object] / 1; he only acquires storage. Thus, accommodation is again required - new.
Similarly, free just frees up memory; this does not apply to objects. Thus, your delete2 is essentially perfect (although using malloc_ptr makes it useless confusing).
On provide
This object has the same [basic.life] / 8 problems as the rest of your examples:
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 (until you break it elsewhere). What for? This 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 static objects with a block scope are initialized in the order in which they appear in the function.
Therefore, we know that pCandidate will be destroyed before storage . Until you save a copy of this shared_ptr in a static variable or otherwise fail to destroy / reset all such shared objects before completion, you should be fine.
That being said, using unsigned char blocks is really pre-C ++ 11. Now we are std::aligned_storage and std::aligned_union . Use them.