这里有人用过c++的“placement new”吗?如果有,为什么?在我看来,它只在内存映射硬件上有用。
当前回答
实际上,实现任何类型的数据结构都需要分配比插入元素数量的最低要求更多的内存(即,除了每次分配一个节点的链接结构之外的任何数据结构)。
以unordered_map、vector或deque等容器为例。这些都为您插入的元素分配了比最低要求更多的内存,以避免为每次插入都需要堆分配。让我们用向量作为最简单的例子。
当你这样做时:
vector<Foo> vec;
// Allocate memory for a thousand Foos:
vec.reserve(1000);
... 其实也造不出一千个foo。它只是为它们分配/保留内存。如果vector没有在这里使用放置new,它将在所有地方默认构造foo,并且必须调用它们的析构函数,即使是对于你从未在第一个位置插入的元素。
分配=建设,解放=毁灭
一般来说,要实现像上面这样的许多数据结构,不能将分配内存和构造元素视为一个不可分割的事情,同样也不能将释放内存和销毁元素视为一个不可分割的事情。
为了避免不必要地向左或向右调用多余的构造函数和析构函数,这就是标准库将std::allocator(在分配/释放内存*时不构造或销毁元素)的思想与使用它的容器分开的原因,这些容器使用放置new手动构造元素,使用显式调用析构函数手动销毁元素。
我讨厌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 new to avoid superfluously invoking constructors and destructors on things unnecessary left and right.
当然,如果你曾经使用自定义分配器来单独分配对象,比如一个free list,那么你通常也会想使用placement 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)),并使用placement new在该区域初始化适当的c++对象,当Variant被设置为持有该类型的值时。(当然,当切换到不同的数据类型时,我事先手动调用对象的析构函数)
我在实时编程中使用过它。我们通常不希望在系统启动后执行任何动态分配(或重新分配),因为无法保证这将花费多长时间。
我能做的是预先分配一大块内存(大到足以容纳类可能需要的任何数量)。然后,一旦我在运行时弄清楚如何构造这些东西,就可以在我想要的地方使用放置new来构造对象。我知道我使用它的一种情况是帮助创建异构循环缓冲区。
这当然不适合胆小的人,但这就是为什么他们把它的语法弄得有点粗糙。
如果你正在构建一个内核,它很有用——你把从磁盘或页表读取的内核代码放在哪里?你得知道该往哪里跳。
或者在其他非常罕见的情况下,比如当你有大量分配的空间,想要把几个结构放在彼此后面。它们可以这样打包,而不需要使用offset()操作符。不过,还有其他的技巧。
我也相信一些STL实现使用了新位置,比如std::vector。它们以这种方式为2^n个元素分配空间,而不需要总是realloc。
我也有个主意。 c++确实有零开销原则。 但是异常不遵循这个原则,所以有时它们会被编译器开关关闭。
让我们来看看这个例子:
#include <new>
#include <cstdio>
#include <cstdlib>
int main() {
struct A {
A() {
printf("A()\n");
}
~A() {
printf("~A()\n");
}
char data[1000000000000000000] = {}; // some very big number
};
try {
A *result = new A();
printf("new passed: %p\n", result);
delete result;
} catch (std::bad_alloc) {
printf("new failed\n");
}
}
我们在这里分配一个大的结构体,检查分配是否成功,然后删除它。
但是如果我们关闭了异常,我们就不能使用try block,并且无法处理new[]失败。
我们怎么做呢?以下是如何做到的:
#include <new>
#include <cstdio>
#include <cstdlib>
int main() {
struct A {
A() {
printf("A()\n");
}
~A() {
printf("~A()\n");
}
char data[1000000000000000000] = {}; // some very big number
};
void *buf = malloc(sizeof(A));
if (buf != nullptr) {
A *result = new(buf) A();
printf("new passed: %p\n", result);
result->~A();
free(result);
} else {
printf("new failed\n");
}
}
使用简单的malloc 检查是否是C方式失败 如果成功了,我们就使用新位置 手动调用析构函数(不能直接调用delete) 电话免费,由于我们叫malloc
UPD @Useless写了一个注释,它向我的视图打开了new(nothrow)的存在,在这种情况下应该使用它,但不是我之前写的方法。请不要使用我之前写的代码。对不起。
我使用它来创建基于内存的对象,其中包含从网络接收到的消息。
推荐文章
- c++11返回值优化或移动?
- Windows和Linux上的c++编译:ifdef开关
- c++中size_t和int的区别是什么?
- 在C和c++中静态变量存储在哪里?
- 为什么标准迭代器范围是[begin, end]而不是[begin, end]?
- c++双地址操作符?(& &)
- 函数标题中的箭头操作符(->)
- 如何在c++中初始化一个向量
- 返回类型为'?:'(三元条件运算符)
- 当分配vector时,它们使用的是堆上的内存还是堆栈上的内存?
- 互斥实例/教程?
- 如何添加一个'或'条件在#ifdef
- 纯虚函数的实现
- 检索Linux上单个进程的CPU使用情况和内存使用情况?
- 为什么在c++中声明enum时使用typedef ?