Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bulk-allocating objects with calling new operator once?

I understand the basics about overloading the new operator for a particular class. However, one thing I do not understand if its possible. Say I have a class like this:

class X{
    int a;
    long b;
    float c;
}

and I wish to pre-create 100 X objects at the very beginning of my program. I would like to call the new operator just the once, to allocate (at least) (4 + 4 + 4?) x 100 = 1200 bytes. Then, whenever X::new() is called, instead of new() (or malloc()) being called, I will return the empty "shell" of an X object and then a, b and c are simply assigned to the data members.

How would I go about doing this? The emphasis on my question is that memory is only taken from the kernel once, when I reserve 1200 bytes for 100 X objects. After the beginning of my program I want the bare minimum to be executed when retrieving an X object "shell"?

like image 226
user997112 Avatar asked Dec 11 '13 16:12

user997112


1 Answers

The emphasis on my question is that memory is only taken from the kernel once, when I reserve 1200 bytes for 100 X objects. After the beginning of my program I want the bare minimum to be executed when retrieving an X object "shell"?

It sounds like what you're trying to do is akin to building a memory pool.

In a memory pool, you pre-allocate a large raw buffer where objects will eventually live. Later you allocate individual objects within this buffer. Doing this has the advantage of not having to allocate memory for each individual object -- all you have to do is construct the object within the pre-allocated space. Since you don't have to go down to the kernel for each Object instantiated this way, there are potentially substantial time savings. The drawback is you are taking responsibility for managing these objects in a more-direct way. The code can be tricky, complex and bug-prone.

To allocate the raw buffer, we simply allocate a char array big enough to hold all the anticipated Objects:

char* buf = new char [1200];

In order to do the second part -- constructing an object within the memory pool -- you need to use placement-new. Assuming buf is the location in the pre-allocated buffer where you want your new object p to be constructed:

Object* p = new (buf) Object();

When it comes time to destroy this object, do not use delete. delete will attempt to deallocate the memory for the object, yielding undefined behavior and a likely crash since you didn't allocate the memory for the object1. Instead, this is one case where you must call the destructor directly:

p->~Object();

Once all the objects have been destroyed, you can then release the buffer using delete[]:

delete [] buf;

Here is a complete example showing how to use placement-new, including construction of the buffer. This uses the (implicitly defined) default constructor. I'll show how to use another constructor later:

#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;
}

Now let's define a constructor for X which takes arguments:

class X
{
public:
    X (int aa, long bb, float cc)
    :
        a (aa),
        b (bb),
        c (cc)
    {
    }
    int a;
    long b;
    float c;
};

Since we've defined a constructor here, the compiler will no longer implicitly define a default constructor for X.

Let's use this new constructor. The arguments to the constructor must now be passed:

   // 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);
    }

Do not use delete1: Technically, the reason that using delete here yields undefined behavior is because delete can only be called with a pointer that was returned from a call to new. Since you didn't new the object, but placement-newed the object, you cannot call delete.

like image 114
John Dibling Avatar answered Oct 03 '22 08:10

John Dibling