Sadly, there are segments of the C++ programming community that seem to equate failure to use the more complex data types of C++ with a lack of programming sophistication, which results in overkill rather than adherence to a prime mantra: Use a solution that is as simple as possible and no simpler."From the book Efficient C++ by Dov Bulka and David Mayhew
class Foo
{
public:
Foo(int a, int b, int c) : a_(a), b_(b), c_(c) {
std::cout << "In Foo constructor\n";
}
~Foo() {
std::cout << "In Foo destructor\n";
}
friend std::ostream &operator<<(std::ostream& os, const Foo& foo);
private:
int a_, b_, c_;
};
std::ostream &operator<<(std::ostream& os, const Foo& foo)
{
return os << "(" << foo.a_ << "," << foo.b_ << "," << foo.c_ << ")";
}
Given the program below, what is printed?
int main(void)
{
Foo *pf1 = new Foo(1, 2, 3);
delete pf1;
return 0;
}
As an aside, the new expression and the delete expression (built into the language) are in the global namespace,
so we can qualify them using the scope resolution operator:
int main(void)
{
Foo *pf1 = ::new Foo(1, 2, 3); // invoke global new
::delete pf1; // invoke global delete
return 0;
}
This is a trivial program and we all know what is printed. But, what is really happening at runtime?
To understand, you have to know the difference between the new expression and operator new.
The new expression does double duty. Informally, it's something like this:
it generates code that is somewhat equivalent to this:Foo *pf1 = ::new Foo(1, 2, 3);
Of course, the syntax for calling a constructor is not quite like that, since you can't call a constructor directly in C++ source code. The concept to understand is that the constructor is told exactly where it's data has been allocated so that it can initialize the data (via offsets from this).void *buffer = ::operator new(sizeof(Foo)); // allocate the proper space using operator new Foo::Foo(buffer, 1, 2, 3); // call the constructor using buffer as this Foo *pf1 = static_cast<Foo *>(buffer); // assign the buffer to the pointer
MSVC++ 6.0 defines operator new like this:
void * operator new( unsigned int cb )
{
void *res = _nh_malloc( cb, 1 );
return res;
}
This makes sense, since all operator new needs to know is the size (count of bytes) to allocate.
That's essentially all it does. Sorry, no magic here.
Essentially, when the compiler sees something like this (built-in types):it is translated to something like this:char *p1 = ::new char[50];or more accurately:char *p1 = ::operator new(50);char *p1 = static_cast<char *>(::operator new(50));
If you step through this code:
you will first find yourself in the code for operator new, then you will end up in the constructor.Foo *pf1 = ::new Foo(1, 2, 3);
Likewise, when the compiler sees this:
it generates code to do something like this:::delete pf1;
MSVC++ 6.0 defines operator delete like this:pf1->~Foo(); // call the destructor operator delete(pf1); // free the memory that was allocated
void __cdecl operator delete(void *p) _THROW0()
{
free(p); // free an allocated object
}
which is also as simple as the corresponding operator new.
Essentially, using the new expression with built-in types is similar to using operator new:
Microsoft's implementationschar *buffer1 = ::new char[12]; // allocate 12 bytes (returns char *) void *buffer2 = ::operator new(12); // allocate 12 bytes (returns void *) ::delete [] buffer1; // free memory allocated by ::new ::operator delete(buffer2); // free memory allocated by ::operator new
So, it is the constructor and destructor that print the messages in the above program, (which we knew by just looking at the code.)
int main(void)
{
Foo *pf1 = ::new Foo(1, 2, 3); // constructor prints the message
::delete pf1; // destructor prints the message
}
Later, we'll see how to "intercept" the call to operator new and operator delete (think overload) and take full
control of allocation and deallocation (think custom memory management).
Compare the details for creating an object on the stack and on the heap:
Creating a Foo object on the stack:
int main(void)
{
Foo f(1, 2, 3); // create Foo on the stack
return 0;
}
Assembly details for stack creation.
Creating a Foo object on the heap:
int main(void)
{
Foo *pf1 = ::new Foo(1, 2, 3); // allocate memory, construct
::delete pf1; // destroy, deallocate memory
}
Assembly details for heap creation.
Self-check: Step through the above code in a debugger to see which functions are being called and in what order (operator new, malloc, operator delete, constructors, destructors, etc.)
Suppose we already have some memory (just lying around, of course) that we want to re-use for our Foo object (instead of allocating another chunk). We can "pass" another parameter in the new expression to indicate our intention. The syntax for this technique:
This is only slightly more complex than our "normal" invocation of new:new (address_to_place_object) type
The implementation for placement new is trivial and looks something like this:new type
void *operator new(size_t, void *address)
{
return address;
}
The actual code in MSVC++ 6.0 looks like this:
#ifndef __PLACEMENT_NEW_INLINE
#define __PLACEMENT_NEW_INLINE
inline void *__cdecl operator new(size_t, void *_P)
{
return (_P);
}
#endif
In essence, by using placement new, what we are telling the new expression is
"Don't worry about allocating the memory, I've already done it for you.
Just construct the object at the address I tell you."
To create a Foo object in pre-allocated memory:
int main(void)
{
int size = sizeof(Foo); // 12 bytes (3 integers, a_, b_, c_)
char *buffer = ::new char[size]; // allocate 12 bytes on the heap
Foo *pf2 = ::new (buffer) Foo(1, 2, 3); // construct a Foo object in buffer (placement new)
::delete [] buffer; // delete char array (not pf2!!) when were done
return 0;
}
Now, what is printed by the program above that uses placement new? Notice a problem?
Actually, we need to handle a slight detail ourselves:
int main(void)
{
char *buffer = ::new char[sizeof(Foo)]; // allocate 12 bytes on the heap
Foo *pf2 = ::new (buffer) Foo(1, 2, 3); // construct a Foo object in buffer (placement)
pf2->~Foo(); // destruct the Foo object that's in buffer
::delete [] buffer; // delete char array (not pf2!!) when were done
return 0;
}
Assembly details for placement new
As a simple experiment, consider the two loops below. By looping 10,000,000 times, what kind of performance difference (if any) would you expect to see?
Loop #1int it = 10000000;
// Using the "normal" new
for (int i = 0; i < it; i++)
{
Foo *pf2 = ::new Foo(i, i, i); // construct a Foo object (allocate/initialize)
// ... do something useful ...
::delete pf2; // destruct the Foo object (deallocate/destructor)
}
Loop #2
// Using placement new
char *buffer = ::new char[sizeof(Foo)]; // allocate 12 bytes on the heap
for (int i = 0; i < it; i++)
{
Foo *pf2 = ::new (buffer) Foo(i, i, i); // construct a Foo object in buffer
// ... do something useful ...
pf2->~Foo(); // destruct the Foo object that's in buffer
}
::delete [] buffer; // delete char array (not pf2!!) when were done
We've managed to move the dynamic allocation for the objects outside the loop. Can we remove the dynamic
allocation and still use placement new?
Now you've got control over how/when objects get constructed:
Foo *pf = ::new Foo(i, i, i); // construct a Foo object (allocate/call constructor)
Foo *pf = ::new (buffer) Foo(i, i, i); // construct a Foo object in buffer
void *buffer = ::operator new(12); // allocate 12 bytes (returns void *)
Important Note: When using placement new, make sure you call the object's destructor and that you call delete on the proper object.
Given our Foo class above, will this compile?
For the sake of demonstration, let's modify the constructor:Foo *pf = ::new Foo[5];
Foo(int a = 0, int b = 0, int c = 0) : a_(a), b_(b), c_(c) {
std::cout << "In Foo constructor\n";
}
Now, what would you expect to see from this program?
The code that the compiler sees for this:Foo *pf = ::new Foo[5]; ::delete [] pf;
is somewhat like this:Foo *pf = ::new Foo[5];
// allocate the proper space for 5 Foo objects
void *buffer = ::operator new(5 * sizeof(Foo));
// construct each object at the correct memory location
for (int i = 0; i < 5; i++)
{
// calculate offset of next object to construct
void *location = static_cast<char *>(buffer) + (i * sizeof(Foo));
// call the constructor using value of location as this
Foo::Foo(location);
}
// assign the fully constructed memory to the pointer
Foo *pf = static_cast<Foo *>(buffer);
And this code:
is seen as something like this:::delete [] pf;
Again, realize that the above code is only pseudo-code using C++ syntax and semantics. You can't code this way and expect it to work correctly.// deconstruct each object at the correct memory location for (int i = 0; i < 5; i++) (pf + i)->~Foo(); ::operator delete(pf1); // free the memory that was allocated
BTW, this is "old-style" C++:
Given your "new" knowledge of new[] and delete[], what might you expect to be printed from this program?Foo *pf = ::new Foo[5]; ::delete [5] pf; // anachronistic use of array size in vector delete
How about this?Foo *pf = ::new Foo[5]; // operator new[] ::delete pf; // operator delete
Foo *pf = ::new Foo; // operator new ::delete [] pf; // operator delete[]
Assembly details for new and new[]
Assembly details for delete and delete[]
Let's go back to our original (non-default) constructor (that prevents us from creating an array of objects):
Foo(int a, int b, int c) : a_(a), b_(b), c_(c) {
std::cout << "In Foo constructor\n";
}
Now, how would you create an array of 5 Foo objects?
Note you could use array notation as well and let the compiler do the pointer arithmetic:int count = 5; // allocate space for 5 Foo objects void *buffer = ::new char[count * sizeof(Foo)]; // point to the raw (uninitialized) memory Foo *pf = static_cast<Foo *>(buffer); // construct Foo objects in place for (int i = 0; i < count; i++) ::new (pf + i) Foo(i, i, i); // ... do something with the objects // destroy the objects manually (in reverse order) for (int j = count - 1; j >= 0; j--) (pf + j)->~Foo(); // free the memory ::delete [] buffer;
// construct Foo objects in place for (int i = 0; i < count; i++) ::new (&pf[i]) Foo(i, i, i); // destroy the objects manually (in reverse order) for (int j = count - 1; j >= 0; j--) pf[j].~Foo();
Self-check: Step through the above code in a debugger to follow the code path at runtime and see that you understand exactly what is happening "behind the scenes."
Our Foo class also uses std::cout to show when they are being called in the examples. These implementations are obviously trivial. We're just "wrapping" the global ::operator new and ::operator delete:
static void *operator new(size_t size) {
std::cout << "In Foo::operator new, size = " << size << "\n";
return ::operator new(size);
}
static void operator delete(void *memory) {
std::cout << "In Foo::operator delete\n";
::operator delete(memory);
}
Now, given this program:
int main(void)
{
Foo *pf1 = new Foo(1, 2, 3); // allocates memory using Foo::operator new
delete pf1; // deallocates memory using Foo::operator delete
std::cout << std::endl;
Foo *pf2 = ::new Foo(4, 5, 6); // allocates memory using global ::operator new
::delete pf2; // deallocates memory using global ::operator delete
return 0;
}
The output is:
Exciting. Truly exciting.In Foo::operator new, size = 12 In Foo constructor: 1,2,3 In Foo destructor: 1,2,3 In Foo::operator delete In Foo constructor: 4,5,6 In Foo destructor: 4,5,6
Note that operator delete can have two parameters. The second parameter will be set to the size of the object (automatically by the compiler) being deleted:
static void operator delete(void *memory, size_t size) {
std::cout << "In Foo::operator delete, size = " << size << "\n";
::operator delete(memory);
}
Using the above overloaded operator delete, our output would look like this:
The purpose of this parameter is for derived classes in which the operator delete might be inherited. (The size of the derived class may not be the same as the base class size.)In Foo::operator new, size = 12 In Foo constructor: 1,2,3 In Foo destructor: 1,2,3 In Foo::operator delete, size = 12 In Foo constructor: 4,5,6 In Foo destructor: 4,5,6
Unfortunately, I get a warning from MSVC++ 6.0 with this syntax, although other compilers compile it just fine (including MSC++ 7.1). You should try this with your compilers to see how they fare.
We can also overload the array versions as well:
static void *operator new[](size_t size) {
std::cout << "In Foo::operator[] new, size = " << size << "\n";
return ::operator new[](size);
}
static void operator delete[](void *memory) {
std::cout << "In Foo::operator delete[]\n";
::operator delete[](memory);
}
Now this code:
prints this:Foo *pf3 = new Foo[3]; // allocates memory using Foo::operator new[] delete [] pf3; // deallocates memory using Foo::operator delete[]
In Foo::operator[] new, size = 40 In Foo constructor: 0,0,0 In Foo constructor: 0,0,0 In Foo constructor: 0,0,0 In Foo destructor: 0,0,0 In Foo destructor: 0,0,0 In Foo destructor: 0,0,0 In Foo::operator delete[]
Here's a more real-world use of overloaded operator new and operator delete using our Foo class above:
operator new:
static void *operator new(size_t size) {
// If we're trying to construct a non-Foo object, just
// use the global ::operator new
if (size != sizeof(Foo))
return ::operator new(size);
Foo *pf = freelist_;
if (freelist_)
freelist_ = freelist_->next_;
else
{
Foo *newpage = static_cast<Foo *>(::operator new(FOOS_PER_PAGE * sizeof(Foo)));
for (int i = 0; i < FOOS_PER_PAGE - 1; ++i)
newpage[i].next_ = &newpage[i + 1];
newpage[FOOS_PER_PAGE - 1].next_ = 0;
pf = newpage;
freelist_ = &newpage[1];
}
return pf;
}
operator delete:
static void operator delete(void *object) {
Foo *pf = static_cast<Foo *>(object);
pf->next_ = freelist_;
freelist_ = pf;
}
We have to modify our Foo class slightly to handle this new implementation:
(In the class declaration):
(In an implementation file):private: int a_, b_, c_; Foo *next_; static const int FOOS_PER_PAGE; static Foo *freelist_; };
Foo *Foo::freelist_ = 0; const int Foo::FOOS_PER_PAGE = 16;
How might the overloaded operator new and operator delete affect the performance of the code below?
int main(void)
{
const int SIZE = 1000000;
int i;
// Allocate lots of pointers to Foo objects
Foo **pf = new Foo*[SIZE];
// Allocate lots of Foo objects
for (i = 0; i < SIZE; ++i)
pf[i] = new Foo(i, i, i);
// Delete lots of Foo objects
for (i = 0; i < SIZE; ++i)
delete pf[i];
return 0;
}
Tests done on a 2.66 GHz P4. Time is in milliseconds:
MS6 GNU3.3 BCC5.6 MS7.1
----------------------------------------
Global new: 390 532 156 453
Foo::new : 36 47 62 93
A more memory-efficient way to provide a convenient "next" pointer:
union {
int a_;
Foo *next_;
};
int b_, c_;
const int BIG_NUM = 1000 * 1000 * 1000 * 2;
// This style expects operator new to return NULL on failure
char *p1 = ::new char[BIG_NUM];
if (!p1)
std::cout << "::operator new failed, returned NULL" << std::endl;
// This style expects operator new to throw an exception on failure
char *p2;
try {
p2 = ::new char[BIG_NUM];
}
catch (std::bad_alloc &e) {
std::cout << "::operator new failed: " << e.what() << std::endl;
}
To suppress ::operator new from throwing:
To force ::operator new to throw:char *p = ::new (std::nothrow) char[BIG_NUM];
Go here
What happens if a constructor throws an exception? Will the destructor be called? For example:
will the compiler generate code to call x.~Foo()?Foo x(1, 2, 3); // Foo's constructor throws an exception
Another example:
int count = 0; // global
class T
{
public:
T() {
std::cout << "In T constructor\n";
// throw exception on 3rd construction
if (++::count == 3)
throw "No soup for you!";
};
~T() {
std::cout << "In T destructor\n";
}
};
Given the class T above, what does this code print?
int main(void)
{
try {
T *pt = ::new T[5];
::delete [] pt;
}
catch (const char *e) {
std::cout << e << std::endl;
}
return 0;
}
The output:
Assembly details.In T constructor In T constructor In T constructor In T destructor In T destructor No soup for you!