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"?
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 Object
s:
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 delete
1: 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-new
ed the object, you cannot call delete
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With