通过调用new运算符批量分配对象一次?

时间:2013-12-11 16:14:30

标签: c++ memory-management malloc new-operator

我理解为特定类重载新运算符的基础知识。但是,有一点我不明白它是否可能。说我有这样的课程:

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} },ab只是分配给数据成员。

我该怎么做呢?强调我的问题是,当我为100个X对象保留1200个字节时,内存只取自内核一次。在我的程序开始之后,我想在检索X对象“shell”时执行最低限度的操作吗?

3 个答案:

答案 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();

当需要销毁此对象时,请勿使用deletedelete将尝试释放对象的内存,导致未定义的行为和可能的崩溃,因为您没有为对象 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",详细讨论原因;简而言之,通用分配器已针对自定义分配器尝试优化的情况进行了优化)。

每当您考虑使用自定义分配方案时,您应该问自己以下内容:您是否实际测量过使用默认分配器对性能的影响(它并不总是那么多,并且不值得冒错误的风险等等。 )?如果它实际上是瓶颈,您是否尝试过其他分配器,例如Hoardtcmallocjemalloc

答案 2 :(得分:0)

您要求做的基本上是实现自己的内存管理器。您可能会发现实现一个初始化为大型内存集的工厂类更容易,只需在请求它们时返回实例。为了防止这些对象被其他人创建,您可以将构造函数标记为私有(并使您的工厂类成为朋友)。