std::aligned_storage::type is POD type. POD type can memcpy. However, What happens if placement new non-trivially-copyable type to std::aligned_storage? Can it memcpy that std::aligned_storage?
non-trivially-copyable type(non-POD type) can NOT memcpy, Behavior is undefined. If std::aligned_storage memcpy non-trivially-copyable type, is it also undefined behavior?
#include <new>
#include <type_traits>
#include <cstring>
#include <iostream>
struct y { int a; } ;
// non-trivially-copyable
struct t
{
y a;
int* p;
t(){ p = new int{ 300 }; }
t( t const& ){ a.a += 100; }
~t(){ delete p; }
};
int main()
{ // Block 1
{
t a; a.a.a = 100;
t b; b.a.a = 200;
// std::memcpy(&b,&a,sizeof(t)); // abort...non-trivially-copyable
}
// Block 2
{
std::aligned_storage_t<sizeof(t),alignof(t)> s;
{
t a;
a.a.a = 100;
std::memcpy(&s,&a,sizeof(t)); // OK...Coincidence? Behavior is undefined?
}
std::cout << static_cast<t*>(static_cast<void*>(&s))->a.a << std::endl; // OK...
}
// Block 3
{
std::aligned_storage_t<sizeof(t),alignof(t)> s1; new( &s1 ) t;
std::aligned_storage_t<sizeof(t),alignof(t)> s2; new( &s2 ) t;
std::memcpy(&s2,&s1,sizeof(t)); // trivially-copyable????
}
}
I thought that it is undefined as well. However, we have work. Is this for coincidence?
First of all: as discussed on this thread, the C++ standard only defines the behaviour of
memcpyfor trivially copyable objects. That thread gives a specific example of how it can break for non-trivially copyable optionsSo, a narrow interpretation of the Standard would say that the mere act of calling
memcpycauses UB.However, a more common sense interpretation would be that it's OK to copy the bytes, but any attempt to treat the target as actually containing an object of the same type would cause UB. Especially in the case where the program depends on side-effects of the destructor, as yours does. The rest of my answer is based on this latter interpretation.
Starting with Block 2:
Since
tis not trivially copyable, we have not created a validtobject ins's storage. So the attempt to usesas if it contained a validtobject certainly causes undefined behaviour. When UB occurs any results can follow, including (but not limited to) it appearing to "work as expected".In Block 1 (if the
memcpyis uncommented):Here we have destructor side-effects. The
memcpyends the lifetime ofa(becausea's storage is re-used). However the code will go on to try and call the destructor ona.In this case, even if the copy "appears to work", your abort probably comes from the double-free of
a.p.If we changed
tto be a non-trivially-copyable type but with no destructor side-effects then this example would be unclear.There is no such double-free in Block 2 because no destructor is ever invoked for the
tstored ins.Block 3:
This is similar to Block 2: the
memcpydoes not create an object. It "appears to work" because you never invoke destructors for the objects in the aligned_storage.In fact, under our common-sense interpretation of
memcpy, there is no UB here because you never attempted to use the result of copying the bytes and the target does not have a destructor called on it. (If you copied yourcoutline to here, it would cause UB for the same reason as in Block 2).Related discussion: Even for trivially copyable classes it is still murky
The C++ standard is unclear around the issues of when object lifetime begins for objects in
malloc'd space oraligned_storage. There was a submission N3751 recognizing that this needs cleanup but there is still a lot of work to do.In your Block 2, the lifetime has not begun for
s. This is becausethas non-trivial initialization. (This is actually not clearly stated by the C++ standard either). However Block 1'sais an object whose lifetime has begun.N3751 proposes that (if
twere trivially copyable) then thememcpywould in fact begin the lifetime ofs.