这里有没有人使用过C ++的“ placement new”?如果是这样,那是为了什么?在我看来,它仅在内存映射的硬件上有用。
p = pt
使用Point
而不是使用赋值运算符new(&p) Point(pt)
呢?我不知道两者之间的区别。前者会operator=
在Point上调用,而后者会在上调用复制构造函数Point
吗?但是我仍然不清楚为什么一个要比另一个更好。
U::operator=
刚刚被调用,则不是这种情况。
这里有没有人使用过C ++的“ placement new”?如果是这样,那是为了什么?在我看来,它仅在内存映射的硬件上有用。
p = pt
使用Point
而不是使用赋值运算符new(&p) Point(pt)
呢?我不知道两者之间的区别。前者会operator=
在Point上调用,而后者会在上调用复制构造函数Point
吗?但是我仍然不清楚为什么一个要比另一个更好。
U::operator=
刚刚被调用,则不是这种情况。
Answers:
当您需要构造一个对象的多个实例时,您可能需要这样做以进行优化,并且在每次需要一个新实例时不重新分配内存会更快。取而代之的是,即使您不想一次使用所有内存,对可以容纳多个对象的一块内存执行单个分配可能更有效。
DevX就是一个很好的例子:
标准C ++还支持放置新运算符,该运算符可在预分配的缓冲区上构造一个对象。这在构建内存池,垃圾回收器或仅在性能和异常安全至关重要时非常有用(因为已经分配了内存,因此没有分配失败的危险,并且在预分配的缓冲区上构造对象所花费的时间更少) :
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
您可能还想确保关键代码的某些部分(例如,在起搏器执行的代码中)不会出现分配失败。在这种情况下,您可能希望先分配内存,然后在关键部分中使用new放置。
您不应该释放使用内存缓冲区的每个对象。相反,您应该只删除[]原始缓冲区。然后,您必须手动调用类的析构函数。有关此问题的好的建议,请参阅Stroustrup的FAQ,有关以下内容:是否存在“展示位置删除”?
delete[]
原始char
缓冲区是未定义的行为。使用放置new
已char
通过重新使用原始对象的存储空间来结束它们的生命周期。如果现在调用delete[] buf
所指向的对象的动态类型不再与它们的静态类型匹配,那么您将具有不确定的行为。使用operator new
/ operator delete
分配原先要由放置使用的原始内存更为一致new
。
#include <new>
。
我们将其与自定义内存池一起使用。只是一个草图:
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, ...);
现在,您可以将对象聚类到一个内存区域中,选择一个非常快但没有解除分配的分配器,使用内存映射以及您希望通过选择池并将其作为参数传递给对象放置的其他任何语义新的运营商。
allocate()
某个地方吗?
我已经在实时编程中使用了它。系统启动后,我们通常不希望执行任何动态分配(或取消分配),因为无法保证这将花费多长时间。
我可以做的是预分配很大的内存(足够大,可以容纳该类可能需要的任何数量的内存)。然后,一旦我在运行时弄清楚了如何构造事物,就可以将placement new用于在我想要的地方构造它们。我知道我用过的一种情况是帮助创建一个异构的循环缓冲区。
当然,这并不是出于胆小,这就是为什么他们使它的语法有些粗糙。
我用它来构造通过alloca()在堆栈上分配的对象。
shameless plug:我在这里博客了。
boost::array
。您可以对此进行扩展吗?
我用它来创建Variant类(即,一个可以表示单个值的对象,该值可以是许多不同类型之一)。
如果Variant类支持的所有值类型都是POD类型(例如int,float,double,bool),则标记的C样式联合就足够了,但是如果您希望某些值类型成为C ++对象(例如std :: string),C联合功能将不起作用,因为非POD数据类型可能不会声明为联合的一部分。
因此,我分配了一个足够大的字节数组(例如sizeof(the_largest_data_type_I_support)),并在Variant设置为保留该类型的值时,使用new放置初始化该区域中的相应C ++对象。(当然,当切换到其他非POD数据类型时,布局会事先删除)
new
该ctor 可能会使用放置来初始化其非POD子类。参考:stackoverflow.com/a/33289972/2757035使用任意大字节数组重塑此轮子是一件令人印象深刻的杂技,但似乎完全没有必要,那么,我错过了什么?:)
当您要重新初始化全局或静态分配的结构时,它也很有用。
旧的C方法memset()
用于将所有元素设置为0。由于vtables和自定义对象构造函数的存在,您无法在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.
}
实际上,实现某种类型的数据结构所分配的内存要比插入的元素数量的最低要求要多(即,除了一个链接结构同时分配一个节点之外的其他任何事物),实际上是一种要求。
取容器,如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 ++容器,因此我倾向于大量使用它。其中包括一个我在几十年前构建的小型矢量实现,以避免在常见情况下进行堆分配,还提供了内存效率较高的Trie(一次不会分配一个节点)。在这两种情况下,我都无法真正使用现有容器来实现它们,因此我不得不placement new
避免在不必要的左右操作上不必要地调用构造函数和析构函数。
自然,如果您曾经使用过自定义分配器来分别分配对象(如空闲列表),那么通常也希望使用placement new
,就像这样(基本示例不会打扰到异常安全性或RAII):
Foo* foo = new(free_list.allocate()) Foo(...);
...
foo->~Foo();
free_list.free(foo);
如果要构建内核,这将很有用-您将从磁盘或页表中读取的内核代码放在哪里?您需要知道跳到哪里。
或者在其他非常罕见的情况下,例如当您有分配的房间负载并且想要在彼此之间放置一些结构时。可以通过这种方式打包它们,而无需offsetof()运算符。但是,还有其他技巧。
我也相信某些STL实现会使用新的位置,例如std :: vector。他们以这种方式为2 ^ n个元素分配空间,而不必总是重新分配。
我认为这还没有得到任何答案,但是新放置的另一个很好的例子和用法是减少内存碎片(通过使用内存池)。这在嵌入式和高可用性系统中特别有用。在后一种情况下,这一点特别重要,因为对于必须运行24/365天的系统,没有碎片非常重要。此问题与内存泄漏无关。
即使使用了非常好的malloc实现(或类似的内存管理功能),也很难长时间处理碎片。在某些时候,如果您不能巧妙地管理内存预留/释放调用,您可能会遇到很多难以重用的小缺口(分配给新的预留)。因此,在这种情况下使用的解决方案之一是使用内存池为应用程序对象预先分配内存。之后,每次您需要某些对象的内存时,只需使用新位置在已保留的内存上创建一个新对象。
这样,一旦您的应用程序启动,您就已经保留了所有需要的内存。所有新的内存保留/释放都将分配到分配的池(您可能有几个池,每个不同的对象类一个)。在这种情况下,不会发生内存碎片,因为没有间隙,并且系统可以运行很长时间(数年)而不会出现碎片。
我在实践中特别针对VxWorks RTOS看到了这一点,因为它的默认内存分配系统遭受了很多碎片的困扰。因此,在项目中基本上禁止通过标准的new / malloc方法分配内存。所有内存预留都应转到专用内存池。
std::vector<>
之所以使用它,是因为std::vector<>
通常分配的内存比objects
中的要多vector<>
。
我已经看到它用作“动态类型”指针的轻微性能破解(在“内幕”部分中):
但是,这是我用来提高小型类型的快速性能的技巧:如果所保存的值可以放入void *内,我实际上不必理会分配新对象,而是使用new位置将其强制放入指针本身。
void*
占用8个字节。这是一个有点傻点八字节void*
的一个字节bool
。但它完全有可能实际的覆盖bool
上void*
,很像union { bool b; void* v }
。您需要某种方式来知道您称为a的东西void*
实际上是a bool
(或short
,或a float
等等)。我链接到的文章介绍了如何执行此操作。并且,为了回答最初的问题,放置new
是用于bool
在void*
期望的a 处创建(或其他类型)的功能(使用广播稍后用于获取/修改值)。
请参阅xll项目中的fp.h文件,该文件位于http://xll.codeplex.com。它为希望随身携带其尺寸的阵列解决了“编译器无用的笨拙”问题。
typedef struct _FP
{
unsigned short int rows;
unsigned short int columns;
double array[1]; /* Actually, array[rows][columns] */
} FP;
这是C ++就地构造函数的杀手级用途:对齐缓存行以及2边界的其他幂。这是我的超快速指针对齐算法,可以使用5个或更少的单周期指令来处理2个边界的任意幂:
/* Quickly aligns the given pointer to a power of two boundary IN BYTES.
@return An aligned pointer of typename T.
@brief Algorithm is a 2's compliment trick that works by masking off
the desired number in 2's compliment and adding them to the
pointer.
@param pointer The pointer to align.
@param boundary_byte_count The boundary byte count that must be an even
power of 2.
@warning Function does not check if the boundary is a power of 2! */
template <typename T = char>
inline T* AlignUp(void* pointer, uintptr_t boundary_byte_count) {
uintptr_t value = reinterpret_cast<uintptr_t>(pointer);
value += (((~value) + 1) & (boundary_byte_count - 1));
return reinterpret_cast<T*>(value);
}
struct Foo { Foo () {} };
char buffer[sizeof (Foo) + 64];
Foo* foo = new (AlignUp<Foo> (buffer, 64)) Foo ();
现在不只是在脸上露出微笑(:-)。我♥♥♥C ++ 1x