当前位置:  开发笔记 > 编程语言 > 正文

"放置新"有什么用途?

如何解决《"放置新"有什么用途?》经验,为你挑选了18个好方法。

有没有人曾经使用过C++的"贴牌新品"?如果是这样,那该怎么办?在我看来,它只对内存映射硬件有用.



1> Brian R. Bon..:

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

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

DevX给出了一个很好的例子:

标准C++还支持placement 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

您可能还希望确保在关键代码的某个部分(例如,由起搏器执行的代码)中没有分配失败.在这种情况下,您需要先分配内存,然后在临界区中使用placement new.

放置新的释放

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


由于您需要此功能来有效地实现容器对象(如矢量),因此不会弃用它.如果您没有构建自己的容器,则不需要使用此功能.
我肯定会跳过在心脏起搏器中使用堆:-)
记住#include 也是非常重要的,否则你可能会在一些不能自动识别位置的平台上遇到一些可怕的麻烦
严格地说,在原始`char`缓冲区上调用`delete []`是未定义的行为.使用放置`new`通过重新使用它们的存储来结束原始`char`对象的生命周期.如果现在调用`delete [] buf`,则指向的对象的动态类型不再与其静态类型匹配,因此您有未定义的行为.使用`operator new` /`operator delete`分配原始内存以供放置`new`使用更加一致.
@RamonZarazua错误的标题,它是`#include `.
区分operator new()和new()运算符是有益的.前者分配,而后者构造.
这是预先分配缓冲区的一个坏例子.所示的是预分配的缓冲区的固定大小的头部.缓冲区仍然在堆上.
我认为绝对不会弃用.

2> Don Wakefiel..:

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

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, ...);

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


@jdkoftinoff您是否有指向实际代码示例的链接?对我来说似乎很有趣!

3> MSN..:

如果要将分配与初始化分开,这很有用.STL使用placement new来创建容器元素.



4> T.E.D...:

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

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

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



5> Ferruccio..:

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

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


@Ferruccio这很酷,我注意到你的宏虽然稍微不安全,但是大小可能是一种表情.例如,如果传入x + 1,则将其扩展为sizeof(type)*x + 1,这将是不正确的.您需要将宏括起来以使其更安全.

6> 小智..:

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



7> Jeremy Fries..:

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

如果Variant类支持的所有值类型都是POD类型(例如int,float,double,bool),那么标记的C样式联合就足够了,但是如果你想要一些值类型是C++对象(例如std :: string),C union特性不会这样做,因为非POD数据类型可能不会被声明为union的一部分.

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


您在C++ 11之前错过了所有C++版本,在许多情况下仍然需要支持.:)

8> nimrodm..:

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

旧的C方式memset()用于将所有元素设置为0.由于vtable和自定义对象构造函数,您无法在C++中执行此操作.

所以我有时会使用以下内容

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



9> RichardBruce..:

在序列化时(例如使用boost :: serialization),Placement new也非常有用.在10年的c ++中,这只是我需要新的第二个案例(如果你包括访谈,则为第三个)).



10> mstrobl..:

如果您正在构建内核,那么它​​是非常有用的 - 您在哪里放置从磁盘或页面表中读取的内核代码?你需要知道去哪里.

或者在其他非常罕见的情况下,例如当你有足够的分配空间并希望将一些结构放在彼此之后.它们可以通过这种方式打包,而无需offsetof()运算符.不过,还有其他一些技巧.

我也相信一些STL实现使用了新的贴图,比如std :: vector.它们为这两个元素分配空间,并且不需要总是重新分配.


我学习操作系统基础知识的操作系统是用C++编写的:http://sweb.sourceforge.net

11> rkachach..:

我认为没有任何答案突出显示这一点,但新位置的另一个好例子和用法是减少内存碎片(通过使用内存池).这在嵌入式和高可用性系统中特别有用.在最后一种情况下,它特别重要,因为对于必须运行24/365天的系统而言,没有碎片是非常重要的.这个问题与内存泄漏无关.

即使使用了非​​常好的malloc实现(或类似的内存管理功能),也很难长时间处理碎片.在某些时候,如果你不巧妙地管理内存预留/释放呼叫,你可能会遇到很多难以重用的小差距(分配给新的预留).因此,在这种情况下使用的解决方案之一是使用内存池为应用程序对象预先分配内存.每当你需要某个对象的内存后,你只需使用新的位置在已经保留的内存上创建一个新对象.

这样,一旦您的应用程序启动,您已经保留了所有必需的内存.所有新的内存预留/释放都将转到已分配的池中(您可能有多个池,每个池对应一个不同的对象类).在这种情况下不会发生内存碎片,因为没有间隙,您的系统可以运行很长时间(几年)而不会受到碎片的影响.

我在实践中专门为VxWorks RTOS看到了这一点,因为它的默认内存分配系统受到很多碎片的影响.因此,在项目中基本上禁止通过标准的new/malloc方法分配内存.所有内存预留应该转到专用内存池.



12> Dragon Energ..:

它实际上需要实现任何类型的数据结构,该数据结构分配的内存多于插入的元素数量所需的最小内存(即,除了一次分配一个节点的链接结构之外的任何内容).

就拿像集装箱unordered_map,vectordeque.这些都分配了比您目前插入的元素所需的最少内存,以避免每次插入都需要堆分配.让我们vector用作最简单的例子.

当你这样做时:

vector vec;

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

......实际上并没有构建一千个Foos.它只是为它们分配/保留内存.如果vector没有在这里使用placement new,那么它将默认构建Foos在整个地方,并且必须调用它们的析构函数,即使对于你从未首先插入的元素也是如此.

分配!=建筑,释放!=毁灭

一般来说,要实现如上所述的许多数据结构,你不能将分配内存和构造元素视为一个不可分割的东西,同样你也不能将释放内存和破坏元素视为一个不可分割的东西.

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

我讨厌设计,std::allocator但这是一个不同的主题,我会避免咆哮.:-D

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

当然,如果您使用自定义分配器来单独分配对象(如空闲列表),那么您通常也希望使用placement new这样的(基本示例不会对异常安全或RAII造成影响):

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



13> Martin Becke..:

我用它来存储具有内存映射文件的对象.
具体的例子是一个图像数据库,它处理大量的大图像(超过可能适合内存).



14> Andreas Magn..:

它的使用std::vector<>,因为std::vector<>通常比分配有更多的内存objectsvector<>.



15> Max Lybbert..:

我已经看到它用作"动态类型"指针的轻微性能黑客(在"引擎盖下"部分):

但是这里有一个棘手的技巧我用来获得小类型的快速性能:如果保持的值可以适合void*,我实际上并不打算分配一个新对象,我使用placement new强制它进入指针本身.



16> Steve Fallow..:

我用它来创建基于包含从网络接收的消息的内存的对象.



17> xtofl..:

通常,使用placement new来摆脱"普通新"的分配成本.

我使用它的另一个场景是我想要访问仍然要构造的对象的指针,以实现每个文档的单例.



18> kralyk..:

除了其他用途之外,它在使用共享内存时可能很方便......例如:http://www.boost.org/doc/libs/1_51_0/doc/html/interprocess/synchronization_mechanisms.html#interprocess.synchronization_mechanisms.conditions. conditions_anonymous_example

推荐阅读
可爱的天使keven_464
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有