I am curious about how the following situation is interpreted under current C++ standard, especially in respect to lifetimes etc. Is it undefined behaviour?
First, lets start with the following definition: a relocatable object is an object which is invariant on its actual memory location — that is, its state stays the same regardless of the value of the pointer this. Assume that we have a relocatable type Relocatable (its definition is irrelevant for the example).
Then we have the following code (C++17):
typedef std::aligned_storage_t<sizeof(Relocatable)> Storage;
// construct an instance of a relocatable within a storage
auto storage0 = new Storage();
new(storage0) Relocatable(...);
{
// obj is a valid reference
// should use std::launder() here, but clang doesn't have it yet
Relocatable& obj = *reinterpret_cast<Relocatable*>(storage0);
}
// move the storage
auto storage1 = new Storage();
memcpy(storage1, storage0, sizeof(Storage));
delete storage0;
{
// ?????? what does the standard say about this?
Relocatable& obj = *reinterpret_cast<Relocatable*>(storage1);
}
This works with both GCC and Clang as expected (the object simply continues to exist in the new storage). However, I am not entirely sure whether the standard is ok with this. Technically, the lifetime of the object has not ended (destructor has been not called) and there hasn't been any access to the object in the old location after the memcpy() call. Also, there exist no references/pointers to the old location. Still, given that C++ seems to treat object identity and object storage as the same thing most of the time, there might be a reason why this is prohibited. Thanks in advance for all the insightful comments.
Edit: It has been suggested that Why would the behavior of std::memcpy be undefined for objects that are not TriviallyCopyable? is a duplicate of this questions. I am not sure it is. First of all, I am memcpying the storage, not the object instance. Second, std::is_trivially_copyable<Relocatable>::value
actually evaluates to true
for all practically relevant applications.
P.S. There is actually a good practical reason why I am asking this. Sometimes it is useful to have objects which can only exist within their container — they are not copyable and not moveable. For instance, I am currently designing an optimized tree data structure with such a properties — tree nodes can only exist within the tree storage, they can't be moved out or copied — all operations on them are carried out via short-lived references. To prevent programmer mistakes (accidental copies/moves), I am deleting both the copy and the move constructor. Which has the rather unfortunate consequence that the nodes can't be stored within a std::vector. Placement new and explicitly managed storage can be used to bypass this limitation — but of course I wouldn't want to do something that is not ok according to the standard.