我理解为特定类重载新运算符的基础知识。但是,有一点我不明白它是否可能。说我有这样的课程:
class X{
int a;
long b;
float c;
}
我想在程序的最开始预先创建100个X对象。我想调用new运算符一次,分配(至少)(4 + 4 + 4?)x 100 = 1200字节。然后,每当调用X::new()
而不是new()
(or malloc()
)被调用时,我将返回X
对象的空“shell”,然后{{1} },a
和b
只是分配给数据成员。
我该怎么做呢?强调我的问题是,当我为100个X对象保留1200个字节时,内存只取自内核一次。在我的程序开始之后,我想在检索X对象“shell”时执行最低限度的操作吗?
答案 0 :(得分:7)
强调我的问题是记忆只取自 内核一次,当我为100个X对象保留1200个字节时。之后 我的程序的开头我希望最小的时候执行 检索X对象“shell”?
听起来你正在尝试做的就像建立一个memory pool。
在内存池中,您预先分配一个大的原始缓冲区,最终对象将存在。稍后您将在此缓冲区中分配单个对象。这样做的好处是不必为每个单独的对象分配内存 - 您所要做的就是在预分配的空间内构造对象。由于您不必以这种方式实例化每个Object
内核,因此可能会节省大量时间。缺点是您负责以更直接的方式管理这些对象。代码可能很棘手,复杂且容易出错。
要分配原始缓冲区,我们只需分配一个足够大的char
数组来保存所有预期的Object
:
char* buf = new char [1200];
为了完成第二部分 - 在内存池中构建对象 - 您需要使用placement-new。假设buf
是预分配缓冲区中您希望构建新对象p
的位置:
Object* p = new (buf) Object();
当需要销毁此对象时,请勿使用delete
。 delete
将尝试释放对象的内存,导致未定义的行为和可能的崩溃,因为您没有为对象 1 分配内存。相反,这是您必须直接调用析构函数的一种情况:
p->~Object();
一旦所有对象都被销毁,您就可以使用delete[]
:
delete [] buf;
这是一个完整的示例,展示了如何使用展示位置 - new
,包括构建缓冲区。这使用(隐式定义的)默认构造函数。我稍后将展示如何使用另一个构造函数:
#include <cstdlib>
#include <new> // required for placement-new
#include <iostream>
class X
{
public:
int a;
long b;
float c;
};
int main()
{
// construct the memory pool's buffer
char* buf = new char [sizeof(X) * 1000]; // enough memory for 1000 instances of X
// Instantiate 1000 instances of X using placement-new
for (size_t i = 0; i < 1000; ++i)
{
// Where in the memory pool shoudl we put this?
char* buf_loc = buf + (sizeof(X) * i);
// Construct a new X at buf_loc
X* new_x = new (buf_loc) X;
}
// Do something with those instances
for (size_t i = 0; i < 1000; ++i)
{
// Where is the object?
char* buf_loc = buf + (sizeof(X) * i);
X* my_x = reinterpret_cast <X*> (buf_loc); // this cast is safe because I *know* that buf_loc points to an X
// Let's assign some values and dump them to screen
my_x->a = i;
my_x->b = 420000 + i;
my_x->c = static_cast <float> (i) + 0.42;
std::cout << "[" << i << "]\t" << my_x->a << "\t" << my_x->b << "\t" << my_x->c << "\n";
}
// Destroy the X's
for (size_t i = 0; i < 1000; ++i)
{
// Where is the object?
char* buf_loc = buf + (sizeof(X) * i);
X* my_x = reinterpret_cast <X*> (buf_loc); // this cast is safe because I *know* that buf_loc points to an X
// Destroy it
my_x->~X();
}
// Delete the memory pool
delete [] buf;
}
现在让我们为X
定义一个带参数的构造函数:
class X
{
public:
X (int aa, long bb, float cc)
:
a (aa),
b (bb),
c (cc)
{
}
int a;
long b;
float c;
};
由于我们在这里定义了一个构造函数,编译器将不再为X
隐式定义默认构造函数。
让我们使用这个新的构造函数。现在必须传递构造函数的参数:
// Instantiate 1000 instances of X using placement-new
for (size_t i = 0; i < 1000; ++i)
{
// Where in the memory pool shoudl we put this?
char* buf_loc = buf + (sizeof(X) * i);
// Construct a new X at buf_loc
X* new_x = new (buf_loc) X(0,0,0.0f);
}
请勿使用delete
1 :从技术上讲,在此使用delete
产生未定义行为的原因是因为delete
只能使用从new
调用返回的指针调用。由于您没有new
对象,但展示位置 - new
对象,您无法调用delete
。
答案 1 :(得分:2)
这几乎总是一个坏主意(请参阅我的论文"Reconsidering Custom Memory Allocation",详细讨论原因;简而言之,通用分配器已针对自定义分配器尝试优化的情况进行了优化)。
每当您考虑使用自定义分配方案时,您应该问自己以下内容:您是否实际测量过使用默认分配器对性能的影响(它并不总是那么多,并且不值得冒错误的风险等等。 )?如果它实际上是瓶颈,您是否尝试过其他分配器,例如Hoard,tcmalloc或jemalloc?
答案 2 :(得分:0)
您要求做的基本上是实现自己的内存管理器。您可能会发现实现一个初始化为大型内存集的工厂类更容易,只需在请求它们时返回实例。为了防止这些对象被其他人创建,您可以将构造函数标记为私有(并使您的工厂类成为朋友)。