Veritas
Veritas

Reputation: 2210

Swapping storage buffers containing placement new created objects

I recently saw a piece of code which used storage buffers to create objects and then simply swapped the buffers in order to avoid the copying overhead. Here is a simple example using integers:

std::aligned_storage_t<sizeof(int), alignof(int)> storage1;
std::aligned_storage_t<sizeof(int), alignof(int)> storage2;

new (&storage1) int(1);
new (&storage2) int(2);

std::swap(storage1, storage2);

int i1 = reinterpret_cast<int&>(storage1);
int i2 = reinterpret_cast<int&>(storage2);

//this prints 2 1
std::cout << i1 << " " << i2 << std::endl;

This feels like undefined behaviour in the general case (specifically swapping the buffers and then accessing the objects as if they were still there) but I am not sure what the standard says about such usage of storage and placement new. Any feedback is much appreciated!

Upvotes: 3

Views: 124

Answers (1)

Lightness Races in Orbit
Lightness Races in Orbit

Reputation: 385144

I suspect there are a few factors rendering this undefined, but we only need one:

[C++11: 3.8/1]: [..] The lifetime of an object of type T ends when:

  • if T is a class type with a non-trivial destructor (12.4), the destructor call starts, or
  • the storage which the object occupies is reused or released.

All subsequent use is use after end-of-life, which is bad and wrong.

The key is that each buffer is being reused.

So, although I would expect this to work in practice at least for trivial types (and for some classes), it's undefined.


The following may have been able to save you:

[C++11: 3.8/7]: If, after the lifetime of an object has ended and before the storage which the object occupied is reused or released, a new object is created at the storage location which the original object occupied, a pointer that pointed to the original object, a reference that referred to the original object, or the name of the original object will automatically refer to the new object and, once the lifetime of the new object has started, can be used to manipulate the new object [..]

…except that you are not creating a new object.

It may or may not be worth noting here that, surprisingly, the ensuing implicit destructor calls are both well-defined:

[C++11: 3.8/8]: If a program ends the lifetime of an object of type T with static (3.7.1), thread (3.7.2), or automatic (3.7.3) storage duration and if T has a non-trivial destructor, the program must ensure that an object of the original type occupies that same storage location when the implicit destructor call takes place; otherwise the behavior of the program is undefined.

Upvotes: 1

Related Questions