这里有人使用过C++的“定位new”吗?如果使用过,是用来做什么的?在我看来,它似乎只对内存映射硬件有用。
这里有人使用过C++的“定位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)。
您不应该释放使用内存缓冲区的每个对象。相反,您应该仅删除原始缓冲区(delete[] original buffer),然后手动调用类的析构函数。有关此内容的良好建议,请参见 Stroustrup 的 FAQ: Is there a "placement delete"?
char
缓冲区上调用 delete[]
是未定义的行为。使用放置 new
已经通过重用它们的存储结束了原始 char
对象的生命周期。如果现在调用 delete[] buf
,则对象指向的动态类型不再匹配其静态类型,因此会产生未定义的行为。更一致的做法是使用 operator new
/ operator delete
来分配原始内存,供放置 new
使用。 - CB Bailey#include <new>
。 - bit2shift我们将其与自定义内存池一起使用。只是一个简单的草图:
class Pool {
public:
Pool() { /* implementation details irrelevant */ };
virtual ~Pool() { /* ditto */ };
virtual void *allocate(size_t);
virtual void deallocate(void *);
static Pool *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, ...);
现在您可以将对象群集在单个内存存储区中,选择一个非常快但不进行解除分配的分配器,使用内存映射以及任何其他语义,通过选择池并将其作为参数传递给对象的放置新运算符来强制执行。
misc_pool
成员函数的语法。它应该是Pool* misc_pool()
吗? - LRDPRDX如果您想将分配与初始化分开,则此技术很有用。STL使用放置new来创建容器元素。
我曾在实时编程中使用过它。我们通常不希望在系统启动后执行任何动态分配(或释放)操作,因为无法保证需要多长时间。
我可以做的是预先分配一大块内存(足够大,以容纳类可能需要的任何数量的任何内容)。然后,在运行时,一旦我弄清楚如何构建这些东西,就可以使用placement new将对象构建在我想要的位置上。我知道我使用它的情况之一是帮助创建异构的 环形缓冲区。
这肯定不适合菜鸟,但这就是为什么语法看起来有点复杂的原因。
我已经使用它来通过alloca()在堆栈上分配对象。
无耻的插播广告: 我在这里写过相关内容。
boost::array
的优势在哪里。你能详细解释一下吗? - GrahamS实际上,实现任何分配比插入的元素所需的最小内存更多的数据结构都是必要的(即除了一次分配一个节点的链接结构之外的任何结构)。
以像unordered_map
、vector
或deque
这样的容器为例。这些容器分配的内存比你迄今为止插入的元素所需的内存更多,以避免每个单独插入都需要进行堆分配。让我们以vector
作为最简单的例子。
当你执行以下操作时:
vector<Foo> vec;
// Allocate memory for a thousand Foos:
vec.reserve(1000);
...这并不实际构造一千个Foos。它只是为它们分配/保留内存。如果vector
在这里没有使用放置new,那么它将默认构造Foos
,并且即使您从未插入过元素,也必须调用它们的析构函数。
分配!=构造,释放!=销毁
一般来说,要实现上述许多数据结构,您不能将分配内存和构造元素视为一个不可分割的事物,同样,您也不能将释放内存和销毁元素视为一个不可分割的事物。
必须在这些想法之间进行区分,以避免不必要地左右超额调用构造函数和析构函数,这就是为什么标准库将std::allocator
的概念(在分配/释放内存*时不构造或销毁元素)与使用它的容器分开,后者使用放置new手动构造元素,并使用显式调用析构函数手动销毁元素。
- 我讨厌
std::allocator
的设计,但这是一个不同的主题,我会避免抱怨。 :-D
总之,我倾向于经常使用它,因为我编写了一些通用的、符合标准的C++容器,这些容器无法建立在现有容器的基础上。其中包括我几十年前构建的一个小型向量实现,以避免在普通情况下进行堆分配,以及一个内存效率高的字典树(不会一次性分配一个节点)。在这两种情况下,我无法使用现有的容器来实现它们,因此我必须使用放置new
来避免在不必要地左右超出事物时多余地调用构造函数和析构函数。
如果您曾经使用自定义分配器单独分配对象,例如空闲列表,那么通常也需要使用放置new
,如下所示(基本示例,不考虑异常安全或RAII):
Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);
我用它创建了一个Variant类(即一个可以表示由多种不同类型中的一个构成的单个值的对象)。
如果Variant类所支持的所有值类型都是POD类型(例如int、float、double、bool),那么标记C式联合就足够了,但是如果你希望一些值类型是C++对象(例如std::string),则C联合特性无法实现,因为非POD数据类型不能作为联合的一部分声明。
所以,我分配了一个足够大的字节数组(例如sizeof(the_largest_data_type_I_support)),并在Variant设置为保存该类型的值时,在该区域使用放置new来初始化适当的C++对象。(当转换到不同的数据类型时,当然我先手动调用对象的析构函数)
new
_来初始化其非POD子类。参考:https://dev59.com/F3RC5IYBdhLWcg3wXPwC#33289972 使用任意大的字节数组重新发明这个轮子是一项令人印象深刻的杂技表演,但似乎完全没有必要,那我错过了什么呢? :) - underscore_d首席极客:BINGO!你完全理解了-这正是它的完美用途。在许多嵌入式环境中,外部限制和/或整体使用场景迫使程序员将对象的分配与其初始化分开。C ++将它们一起称为“实例化”; 但每当必须显式调用构造函数的操作而没有动态或自动分配时,放置new就是做到这一点的方法。对于固定在硬件组件地址(内存映射I / O)上的全局C ++对象或任何由于某种原因必须驻留在固定地址的静态对象来说,这也是完美的方法。
在序列化(例如使用boost::serialization)时,放置new非常有用。在我十年的c++编程生涯中,这只是第二次我需要使用放置new(如果您包括面试,则为第三次:)。
p = pt
并使用Point
的赋值运算符,而要做new(&p) Point(pt)
?我想知道两者之间的区别。前者会调用Point
的operator=
,而后者会调用Point
的复制构造函数吗?但我还不是很清楚为什么一个比另一个更好。 - Andrei-Niculae Petre