简体   繁体   中英

What does C++ standard say about moving live object storage?

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.

PS 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.

So, as with all of these kinds of questions, objects are only created in four situations :

An object is created by a definition ([basic.def]), by a new-expression , when implicitly changing the active member of a union ([class.union]), or when a temporary object is created ([conv.rval], [class.temporary]).

This code:

auto storage1 = new Storage();
memcpy(storage1, storage0, sizeof(Storage));

Gives you an object of type Storage at storage1 , but there is no object of type Relocatable ever created at that point. Hence, this:

Relocatable& obj = *reinterpret_cast<Relocatable*>(storage1);

is undefined behavior. Period.


In order to define behavior for that, we need a fifth mechanism to create an object, such as what is proposed in P0593 :

We propose that at minimum the following operations be specified as implicitly creating objects: [...]

  • A call to memmove behaves as if it

    1. copies the source storage to a temporary area

    2. implicitly creates objects in the destination storage, and then

    3. copies the temporary storage to the destination storage.

    This permits memmove to preserve the types of trivially-copyable objects, or to be used to reinterpret a byte representation of one object as that of another object.

  • A call to memcpy behaves the same as a call to memmove except that it introduces an overlap restriction between the source and destination.

This proposal (or something like it) would be necessary your code well-formed.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM