C++ “placement new”有什么用?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/222557/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 13:50:09  来源:igfitidea点击:

What uses are there for "placement new"?

c++memory-managementnew-operatorplacement-new

提问by Head Geek

Has anyone here ever used C++'s "placement new"? If so, what for? It looks to me like it would only be useful on memory-mapped hardware.

这里有人用过 C++ 的“placement new”吗?如果是,是为了什么?在我看来,它只对内存映射硬件有用。

采纳答案by Brian R. Bondy

Placement new allows you to construct an object in memory that's already allocated.

Placement new 允许您在已分配的内存中构造一个对象。

You may want to do this for optimization when you need to construct multiple instances of an object, and it is faster not to re-allocate memory each time you need a new instance. Instead, it might be more efficient to perform a single allocation for a chunk of memory that can hold multiple objects, even though you don't want to use all of it at once.

当您需要构造一个对象的多个实例时,您可能希望这样做以进行优化,并且每次需要新实例时不重新分配内存会更快。相反,即使您不想一次使用所有对象,为可以容纳多个对象的内存块执行单个分配可能更有效。

DevX gives a good example:

DevX 给出了一个很好的例子

Standard C++ also supports placement new operator, which constructs an object on a pre-allocated buffer. This is useful when building a memory pool, a garbage collector or simply when performance and exception safety are paramount (there's no danger of allocation failure since the memory has already been allocated, and constructing an object on a pre-allocated buffer takes less time):

标准 C++ 还支持放置 new 运算符,它在预先分配的缓冲区上构造一个对象。这在构建内存池、垃圾收集器或仅当性能和异常安全最重要时很有用(因为内存已经分配,​​所以没有分配失败的危险,并且在预先分配的缓冲区上构造对象花费的时间更少) :

char *buf  = new char[sizeof(string)]; // pre-allocated buffer
string *p = new (buf) string("hi");    // placement new
string *q = new string("hi");          // ordinary heap allocation

You may also want to be sure there can be no allocation failure at a certain part of critical code (for instance, in code executed by a pacemaker). In that case you would want to allocate memory earlier, then use placement new within the critical section.

您可能还想确保在关键代码的某个部分(例如,在由起搏器执行的代码中)没有分配失败。在这种情况下,您可能希望更早地分配内存,然后在临界区中使用新的布局。

Deallocation in placement new

新的安置解除分配

You should not deallocate every object that is using the memory buffer. Instead you should delete[] only the original buffer. You would have to then call the destructors of your classes manually. For a good suggestion on this, please see Stroustrup's FAQ on: Is there a "placement delete"?

您不应取消分配使用内存缓冲区的每个对象。相反,您应该仅删除 [] 原始缓冲区。然后您必须手动调用类的析构函数。有关这方面的好建议,请参阅 Stroustrup 的常见问题解答:是否存在“放置删除”

回答by Don Wakefield

We use it with custom memory pools. Just a sketch:

我们将它与自定义内存池一起使用。只是一个草图:

class Pool {
public:
    Pool() { /* implementation details irrelevant */ };
    virtual ~Pool() { /* ditto */ };

    virtual void *allocate(size_t);
    virtual void deallocate(void *);

    static Pool::misc_pool() { return misc_pool_p; /* global MiscPool for general use */ }
};

class ClusterPool : public Pool { /* ... */ };
class FastPool : public Pool { /* ... */ };
class MapPool : public Pool { /* ... */ };
class MiscPool : public Pool { /* ... */ };

// elsewhere...

void *pnew_new(size_t size)
{
   return Pool::misc_pool()->allocate(size);
}

void *pnew_new(size_t size, Pool *pool_p)
{
   if (!pool_p) {
      return Pool::misc_pool()->allocate(size);
   }
   else {
      return pool_p->allocate(size);
   }
}

void pnew_delete(void *p)
{
   Pool *hp = Pool::find_pool(p);
   // note: if p == 0, then Pool::find_pool(p) will return 0.
   if (hp) {
      hp->deallocate(p);
   }
}

// elsewhere...

class Obj {
public:
   // misc ctors, dtors, etc.

   // just a sampling of new/del operators
   void *operator new(size_t s)             { return pnew_new(s); }
   void *operator new(size_t s, Pool *hp)   { return pnew_new(s, hp); }
   void operator delete(void *dp)           { pnew_delete(dp); }
   void operator delete(void *dp, Pool*)    { pnew_delete(dp); }

   void *operator new[](size_t s)           { return pnew_new(s); }
   void *operator new[](size_t s, Pool* hp) { return pnew_new(s, hp); }
   void operator delete[](void *dp)         { pnew_delete(dp); }
   void operator delete[](void *dp, Pool*)  { pnew_delete(dp); }
};

// elsewhere...

ClusterPool *cp = new ClusterPool(arg1, arg2, ...);

Obj *new_obj = new (cp) Obj(arg_a, arg_b, ...);

Now you can cluster objects together in a single memory arena, select an allocator which is very fast but does no deallocation, use memory mapping, and any other semantic you wish to impose by choosing the pool and passing it as an argument to an object's placement new operator.

现在,您可以在单个内存区域中将对象聚集在一起,选择一个非常快但不释放的分配器,使用内存映射以及您希望通过选择池并将其作为参数传递给对象放置的参数来强加的任何其他语义新运营商。

回答by MSN

It's useful if you want to separate allocation from initialization. STL uses placement new to create container elements.

如果您想将分配与初始化分开,这很有用。STL 使用新放置来创建容器元素。

回答by T.E.D.

I've used it in real-time programming. We typically don'twant to perform any dynamic allocation (or deallocation) after the system starts up, because there's no guarantee how long that is going to take.

我在实时编程中使用过它。我们通常不想在系统启动后执行任何动态分配(或解除分配),因为无法保证这需要多长时间。

What I can do is preallocate a large chunk of memory (large enough to hold any amount of whatever that the class may require). Then, once I figure out at runtime how to construct the things, placement new can be used to construct objects right where I want them. One situation I know I used it in was to help create a heterogeneous circular buffer.

我能做的是预先分配一大块内存(大到足以容纳类可能需要的任何数量)。然后,一旦我在运行时弄清楚如何构造事物,placement new 可用于在我想要的地方构造对象。我知道我使用它的一种情况是帮助创建异构循环缓冲区

It's certainly not for the faint of heart, but that's why they make the syntax for it kinda gnarly.

这当然不适合胆小的人,但这就是为什么他们为它制作的语法有点粗糙。

回答by Ferruccio

I've used it to construct objects allocated on the stack via alloca().

我用它来构造通过 alloca() 在堆栈上分配的对象。

shameless plug:I blogged about it here.

无耻的插件:我在这里写了博客。

回答by dorcsssc

Head Geek: BINGO! You got it totally - that's exactly what it's perfect for. In many embedded environments, external constraints and/or the overall use scenario forces the programmer to separate the allocation of an object from its initialization. Lumped together, C++ calls this "instantiation"; but whenever the constructor's action must be explicitly invoked WITHOUT dynamic or automatic allocation, placement new is the way to do it. It's also the perfect way to locate a global C++ object that is pinned to the address of a hardware component (memory-mapped I/O), or for any static object that, for whatever reason, must reside at a fixed address.

Head Geek:宾果游戏!你完全明白了——这正是它的完美之处。在许多嵌入式环境中,外部约束和/或整体使用场景迫使程序员将对象的分配与其初始化分开。放在一起,C++ 称之为“实例化”;但是每当构造函数的操作必须在没有动态或自动分配的情况下显式调用时,placement new 就是这样做的方法。这也是定位固定到硬件组件(内存映射 I/O)地址的全局 C++ 对象或任何静态对象(无论出于何种原因必须驻留在固定地址)的完美方式。

回答by Jeremy Friesner

I've used it to create a Variant class (i.e. an object that can represent a single value that can be one of a number of different types).

我用它创建了一个 Variant 类(即一个可以表示单个值的对象,该值可以是多种不同类型之一)。

If all of the value-types supported by the Variant class are POD types (e.g. int, float, double, bool) then a tagged C-style union is sufficient, but if you want some of the value-types to be C++ objects (e.g. std::string), the C union feature won't do, as non-POD datatypes may not be declared as part of a union.

如果 Variant 类支持的所有值类型都是 POD 类型(例如 int、float、double、bool),那么标记的 C 样式联合就足够了,但是如果您希望某些值类型是 C++ 对象(例如 std::string),C 联合功能将不起作用,因为非 POD 数据类型可能不会被声明为联合的一部分。

So instead I allocate a byte array that is big enough (e.g. sizeof(the_largest_data_type_I_support)) and use placement new to initialize the appropriate C++ object in that area when the Variant is set to hold a value of that type. (And placement delete beforehand when switching away from a different non-POD data type, of course)

因此,我分配了一个足够大的字节数组(例如 sizeof(the_largest_data_type_I_support)),并在将 Variant 设置为保存该类型的值时,使用放置 new 来初始化该区域中适当的 C++ 对象。(当然,当从不同的非 POD 数据类型切换时,预先删除放置)

回答by RichardBruce

Placement new is also very useful when serialising (say with boost::serialization). In 10 years of c++ this is only the second case I've needed placement new for (third if you include interviews :) ).

Placement new 在序列化时也非常有用(比如 boost::serialization)。在 10 年的 C++ 中,这只是我需要新职位的第二种情况(如果包括面试,则为第三种 :))。

回答by Dragon Energy

It's actually kind of required to implement any kind of data structure that allocates more memory than minimally required for the number of elements inserted (i.e., anything other than a linked structure which allocates one node at a time).

实际上,需要实现任何一种数据结构,该结构分配的内存比插入的元素数量所需的内存要多(即,一次分配一个节点的链接结构除外)。

Take containers like unordered_map, vector, or deque. These all allocate more memory than is minimally required for the elements you've inserted so far to avoid requiring a heap allocation for every single insertion. Let's use vectoras the simplest example.

取容器,例如unordered_map, vector, 或deque。这些都分配了比您到目前为止插入的元素所需的最低限度更多的内存,以避免每次插入都需要分配堆。让我们vector作为最简单的例子。

When you do:

当你这样做时:

vector<Foo> vec;

// Allocate memory for a thousand Foos:
vec.reserve(1000);

... that doesn't actually construct a thousand Foos. It simply allocates/reserves memory for them. If vectordid not use placement new here, it would be default-constructing Foosall over the place as well as having to invoke their destructors even for elements you never even inserted in the first place.

...实际上并没有构建一千个 Foos。它只是为它们分配/保留内存。如果vector这里没有使用新的放置,它将Foos在整个地方进行默认构造,并且即使对于您从未插入过的元素也必须调用它们的析构函数。

Allocation != Construction, Freeing != Destruction

分配 != 构建,释放 != 销毁

Just generally speaking to implement many data structures like the above, you cannot treat allocating memory and constructing elements as one indivisible thing, and you likewise cannot treat freeing memory and destroying elements as one indivisible thing.

只是一般来说,要实现上述很多数据结构,不能把分配内存和构造元素当作一件不可分割的事情,同样不能把释放内存和销毁元素当作一件不可分割的事情。

There has to be a separation between these ideas to avoid superfluously invoking constructors and destructors unnecessarily left and right, and that's why the standard library separates the idea of std::allocator(which doesn't construct or destroy elements when it allocates/frees memory*) away from the containers that use it which do manually construct elements using placement new and manually destroy elements using explicit invocations of destructors.

这些想法之间必须分开,以避免不必要地左右调用构造函数和析构函数,这就是为什么标准库将std::allocator(在分配/释放内存时不构造或销毁元素*)的想法与使用它的容器使用placement new 手动构造元素,并使用显式调用析构函数手动销毁元素。

  • I hate the design of std::allocatorbut that's a different subject I'll avoid ranting about. :-D
  • 我讨厌的设计,std::allocator但这是一个不同的主题,我会避免咆哮。:-D

So anyway, I tend to use it a lot since I've written a number of general-purpose standard-compliant C++ containers that could not be built in terms of the existing ones. Included among them is a small vector implementation I built a couple decades ago to avoid heap allocations in common cases, and a memory-efficient trie (doesn't allocate one node at a time). In both cases I couldn't really implement them using the existing containers, and so I had to use placement newto avoid superfluously invoking constructors and destructors on things unnecessary left and right.

所以无论如何,我倾向于经常使用它,因为我已经编写了许多无法根据现有容器构建的通用标准兼容 C++ 容器。其中包括我几十年前构建的一个小型向量实现,以避免在常见情况下进行堆分配,以及一个内存高效的特里树(一次不分配一个节点)。在这两种情况下,我都无法使用现有容器真正实现它们,因此我不得不使用placement new以避免在左右不必要的事情上多余地调用构造函数和析构函数。

Naturally if you ever work with custom allocators to allocate objects individually, like a free list, then you'd also generally want to use placement new, like this (basic example which doesn't bother with exception-safety or RAII):

自然地,如果您曾经使用自定义分配器来单独分配对象,例如空闲列表,那么您通常也希望使用placement new,像这样(基本示例不涉及异常安全或 RAII):

Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);

回答by nimrodm

It's also useful when you want to re-initialize global or statically allocated structures.

当您想要重新初始化全局或静态分配的结构时,它也很有用。

The old C way was using memset()to set all elements to 0. You cannot do that in C++ due to vtables and custom object constructors.

旧的 C 方法memset()用于将所有元素设置为 0。由于 vtables 和自定义对象构造函数,您不能在 C++ 中这样做。

So I sometimes use the following

所以我有时会使用以下

 static Mystruct m;

 for(...)  {
     // re-initialize the structure. Note the use of placement new
     // and the extra parenthesis after Mystruct to force initialization.
     new (&m) Mystruct();

     // do-some work that modifies m's content.
 }