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
memcpy
for 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
memcpy
causes 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
t
is not trivially copyable, we have not created a validt
object ins
's storage. So the attempt to uses
as if it contained a validt
object 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
memcpy
is uncommented):Here we have destructor side-effects. The
memcpy
ends 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
t
to 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
t
stored ins
.Block 3:
This is similar to Block 2: the
memcpy
does 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 yourcout
line 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 becauset
has non-trivial initialization. (This is actually not clearly stated by the C++ standard either). However Block 1'sa
is an object whose lifetime has begun.N3751 proposes that (if
t
were trivially copyable) then thememcpy
would in fact begin the lifetime ofs
.