使用 void* 位作为存储的 placement new

Placement new using void* bits as storage

假设 sizeof(T) <= sizeof(void*),以下是否定义明确/可移植? ...

void* storage = 0;
new (&storage) T(t);

这似乎可以用作明智的小对象优化,如果是的话。

您正在传递一个有效地址,如果指向的内存足够大以包含该对象,并且它的对齐方式与该对象兼容,那么它就是完美的代码。

如果 sizeof(T) <= sizeof(storage),则对齐应该是隐式正确的。但是你可以对它偏执并明确地设置它:

alignas(T) void* storage = 0;

虽然我认为实际上不需要手动设置对齐方式,并且sizeof(T) <= sizeof(storage)意味着可以保证正确对齐,但我不是 100% 确定。

请注意,仅仅因为 storage 的类型是 void* 并不意味着什么。这个新的特定位置由标准定义为:

void* operator new(std::size_t count, void* ptr);

地址参数是void*,表示指向的类型可以是任何类型。唯一的要求是它是有效内存的地址。

但是,如果 storage 超出范围,则如果其中包含的对象需要销毁,您就会被破坏。您需要在 storage 超出范围之前调用析构函数。

还要注意 "placement delete"(如果需要会自动调用,不可能编写执行此操作的代码)实际上永远不会释放内存。所以 storage 在堆栈上仍然很好,即使调用了放置删除(就像构造函数抛出时一样)。

假设您已经检查对齐是否正确,如 Nikos C. 的回答所述:

new 是合法的,但是名称 storage 不能再以任何方式用于指代该对象(除非 T = void *)。你需要写:

T *ptr = new(&storage) T(t);

然后使用ptr

见[basic.life]/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, if:

  • the storage for the new object exactly overlays the storage location which the original object occupied, and
  • the new object is of the same type as the original object (ignoring the top-level cv-qualifiers), and [...]

第二个要点是错误的,所以不能使用原始对象的名称来操作新对象。


注意:您当然不能做 cout << storage; 这样的事情。但是我不清楚这样写是否合法:

new(&storage) T(t);
T *ptr = reinterpret_cast<T>(&storage);

即如果仅使用原始对象的名称来获取其地址,然后使用该地址来操作新对象,这是否算作通过原始对象的名称来操作新对象?在任何一种情况下,都可以使用 post.

顶部建议的代码来避免该问题。