new
Recall that placement new
allows you to specify the memory location used to allocate memory. Chapter 9, “Memory Models and Namespaces,” discusses placement new
in the context of built-in types. Using placement new
with objects adds some new twists. Listing 12.8 uses placement new
along with regular new
to allocate memory for objects. It defines a class with a chatty constructor and destructor so that you can follow the history of objects.
// placenew1.cpp -- new, placement new, no delete
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;
class JustTesting
{
private:
string words;
int number;
public:
JustTesting(const string & s = "Just Testing", int n = 0)
{words = s; number = n; cout << words << " constructed
"; }
~JustTesting() { cout << words << " destroyed
";}
void Show() const { cout << words << ", " << number << endl;}
};
int main()
{
char * buffer = new char[BUF]; // get a block of memory
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // place object in buffer
pc2 = new JustTesting("Heap1", 20); // place object on heap
cout << "Memory block addresses:
" << "buffer: "
<< (void *) buffer << " heap: " << pc2 <<endl;
cout << "Memory contents:
";
cout << pc1 << ": ";
pc1->Show();
cout << pc2 << ": ";
pc2->Show();
JustTesting *pc3, *pc4;
pc3 = new (buffer) JustTesting("Bad Idea", 6);
pc4 = new JustTesting("Heap2", 10);
cout << "Memory contents:
";
cout << pc3 << ": ";
pc3->Show();
cout << pc4 << ": ";
pc4->Show();
delete pc2; // free Heap1
delete pc4; // free Heap2
delete [] buffer; // free buffer
cout << "Done
";
return 0;
}
The program in Listing 12.8 uses new
to create a memory buffer of 512 bytes. It then uses new
to create two objects of type JustTesting
on the heap and attempts to use placement new
to create two objects of type JustTesting
in the memory buffer. Finally, it uses delete
to free the memory allocated by new
. Here is the output:
Just Testing constructed
Heap1 constructed
Memory block addresses:
buffer: 00320AB0 heap: 00320CE0
Memory contents:
00320AB0: Just Testing, 0
00320CE0: Heap1, 20
Bad Idea constructed
Heap2 constructed
Memory contents:
00320AB0: Bad Idea, 6
00320EC8: Heap2, 10
Heap1 destroyed
Heap2 destroyed
Done
As usual, the formatting and exact values for the memory addresses will vary from system to system.
There are a couple problems with placement new
as used in Listing 12.8. First, when creating a second object, placement new
simply overwrites the same location used for the first object with a new object. Not only is this rude, it means that the destructor was never called for the first object. This, of course, would create real problems if, say, the class used dynamic memory allocation for its members.
Second, using delete
with pc2
and pc4
automatically invokes the destructors for the two objects that pc2
and pc4
point to. But using delete []
with buffer
does not invoke the destructors for the objects created with placement new
.
One lesson to be learned here is the same lesson you learned in Chapter 9: It’s up to you to manage the memory locations in a buffer that placement new
populates. To use two different locations, you provide two different addresses within the buffer, making sure that the locations don’t overlap. You can, for example, use this:
pc1 = new (buffer) JustTesting;
pc3 = new (buffer + sizeof (JustTesting)) JustTesting("Better Idea", 6);
Here the pointer pc3
is offset from pc1
by the size of a JustTesting
object.
The second lesson to be learned here is that if you use placement new
to store objects, you need to arrange for their destructors to be called. But how? For objects created on the heap, you can use this:
delete pc2; // delete object pointed to by pc2
delete pc1; // delete object pointed to by pc1? NO!
delete pc3; // delete object pointed to by pc3? NO!
The reason is that delete
works in conjunction with new
but not with placement new
. The pointer pc3
, for example, does not receive an address returned by new
, so delete pc3
throws a runtime error. The pointer pc1
, on the other hand, has the same numeric value as buffer
, but buffer
is initialized using new []
, so it’s freed using delete []
, not delete
. Even if buffer
were initialized by new
instead of new []
, delete pc1
would free buffer
, not pc1
. That’s because the new
/delete
system knows about the 256-byte block that is allocated, but it doesn’t know anything about what placement new
does with the block.
Note that the program does free the buffer:
delete [] buffer; // free buffer
As this comment suggests, delete [] buffer;
deletes the entire block of memory allocated by new
. But it doesn’t call the destructors for any objects that placement new
constructs in the block. You can tell this is so because this program uses chatty destructors, which report the demise of "Heap1"
and "Heap2"
but which remain silent about "Just Testing"
and "Bad Idea"
.
The solution to this quandary is that you must call the destructor explicitly for any object created by placement new
. Normally, destructors are called automatically; this is one of the rare cases that require an explicit call. An explicit call to a destructor requires identifying the object to be destroyed. Because there are pointers to the objects, you can use these pointers:
pc3->~JustTesting(); // destroy object pointed to by pc3
pc1->~JustTesting(); // destroy object pointed to by pc1
Listing 12.9 fixes Listing 12.8 by managing memory locations used by placement new
and by adding appropriate uses of delete
and of explicit destructor calls. One important fact is the proper order of deletion. The objects constructed by placement new
should be destroyed in order opposite that in which they were constructed. The reason is that, in principle, a later object might have dependencies on an earlier object. And the buffer used to hold the objects should be freed only after all the contained objects are destroyed.
// placenew2.cpp -- new, placement new, no delete
#include <iostream>
#include <string>
#include <new>
using namespace std;
const int BUF = 512;
class JustTesting
{
private:
string words;
int number;
public:
JustTesting(const string & s = "Just Testing", int n = 0)
{words = s; number = n; cout << words << " constructed
"; }
~JustTesting() { cout << words << " destroyed
";}
void Show() const { cout << words << ", " << number << endl;}
};
int main()
{
char * buffer = new char[BUF]; // get a block of memory
JustTesting *pc1, *pc2;
pc1 = new (buffer) JustTesting; // place object in buffer
pc2 = new JustTesting("Heap1", 20); // place object on heap
cout << "Memory block addresses:
" << "buffer: "
<< (void *) buffer << " heap: " << pc2 <<endl;
cout << "Memory contents:
";
cout << pc1 << ": ";
pc1->Show();
cout << pc2 << ": ";
pc2->Show();
JustTesting *pc3, *pc4;
// fix placement new location
pc3 = new (buffer + sizeof (JustTesting))
JustTesting("Better Idea", 6);
pc4 = new JustTesting("Heap2", 10);
cout << "Memory contents:
";
cout << pc3 << ": ";
pc3->Show();
cout << pc4 << ": ";
pc4->Show();
delete pc2; // free Heap1
delete pc4; // free Heap2
// explicitly destroy placement new objects
pc3->~JustTesting(); // destroy object pointed to by pc3
pc1->~JustTesting(); // destroy object pointed to by pc1
delete [] buffer; // free buffer
cout << "Done
";
return 0;
}
Here is the output of the program in Listing 12.9:
Just Testing constructed
Heap1 constructed
Memory block addresses:
buffer: 00320AB0 heap: 00320CE0
Memory contents:
00320AB0: Just Testing, 0
00320CE0: Heap1, 20
Better Idea constructed
Heap2 constructed
Memory contents:
00320AD0: Better Idea, 6
00320EC8: Heap2, 10
Heap1 destroyed
Heap2 destroyed
Better Idea destroyed
Just Testing destroyed
Done
The program in Listing 12.9 places the two placement new
objects in adjacent location and calls the proper destructors.
By now, you’ve encountered several programming techniques for dealing with various class-related problems, and you may be having trouble keeping track of all of them. So the following sections summarize several techniques and when they are used.
3.138.179.100