The new operator in C++
Written by Eli Bendersky   
Tuesday, 29 March 2011
Article Index
The new operator in C++
Allocation
Deleting an allocated object

 

At first glance, dynamic allocation of objects in C++ is simple: new to allocate, delete to deallocate, and you’re done. However, under the hood, the issue is much more complex and allows a considerable level of customization.

This may not be important for simple applications, but is essential when you need better control of memory in your code, whether by writing a custom allocator, some kind of advanced memory management scheme or a specialized garbage collector.

This article doesn’t aim to be a comprehensive manual, but a brief overview of the various ways memory can be allocated in C++. It’s not basic, and assumes a good familiarity with the language.

Raw operator new

Let’s start with the raw operator new. Consider this code, which allocates space for 5 integers and returns a pointer to it:

int* v = static_cast<int*>
(::operator new(5 * sizeof(*v)));

Notice that I wrote :: before operator new explicitly although it's not strictly required in this case. I regard this is a good practice, especially when used inside overloaded operator new methods to avoid ambiguity.

When called like this, operator new acts as a raw memory allocator, similar to malloc. The above line is conceptually equivalent to:

int* v = static_cast<int*>
(malloc(5 * sizeof(*v)));

Freeing memory allocated with the raw operator new is done with the raw operator delete:

::operator delete(v);

Would you ever use the raw new and delete functions?

Yes, in some rare cases, as I’ll demonstrate later in the article. Why use them instead of the old and trusted malloc and free? One good reason is that you want to keep your code wholly in the C++ domain. Mixing new with free (or malloc with delete) is a big NO NO. Another reason is that you can overload or override these functions if you need to. Here’s an example:

void* operator new(size_t sz)
 throw (std::bad_alloc) { cerr << "allocating " << sz << " bytes\n"; void* mem = malloc(sz); if (mem) return mem; else throw std::bad_alloc(); } void operator delete(void* ptr) throw() { cerr << "deallocating at " << ptr << endl; free(ptr); }

In general, keep in mind that the global operator new function is called when the new operator is used to allocate objects of built-in types, objects of class type that do not contain user-defined operator new functions, and arrays of any type. When the new operator is used to allocate objects of a class type where an operator new is defined, that class’s operator new is called.

And this brings us to classes with operator new.

Class-specific operator new

People sometimes wonder what’s the difference between "operator new" and the "new operator".

The former refers to either an overloaded operator new, global or class-specific, or the raw operator new function presented earlier. The latter refers to the built-in C++ new operator you usually employ to allocate memory, as in:

Car* mycar = new Car;

C++ supports operator overloading, and one of the operators it lets us overload is new. Here’s an example:

class Base
{
public:
 void* operator new(size_t sz)
 {
  cerr << "new " << sz << " bytes\n";
  return ::operator new(sz);
 }

void operator delete(void* p) { cerr << "delete\n"; ::operator delete(p); } private: int m_data; }; class Derived : public Base { private: int m_derived_data; vector<int> z, y, x, w; }; int main() { Base* b = new Base; delete b; Derived* d = new Derived; delete d; return 0; }

This prints:

new 4 bytes
delete
new 56 bytes
delete

The overloaded operator new and operator delete in the base class are also inherited by derived classes. As you can see, the operator new method gets the correct size to allocate in both cases. Note also that to actually allocate the memory, it uses ::operator new, the raw operator new described in the previous section. The double-colon in the call is essential in this case to avoid infinite recursion (without it the method would just call itself).

Why would you overload operator new for a class? There are many reasons.

  • Performance: the default memory allocator is designed to be general purpose. Sometimes you have very specific objects you want to allocate, and by customizing the way they’re allocated you can speed up memory management considerably. A lot of books and articles discuss this issue. Notably, chapter 4 in "Modern C++ Design" presents a very well designed and implemented custom allocator for small objects.
  • Debugging & statistics: having full control of the way memory is allocated and released provides great flexibility for debugging, statistics and performance analysis. You can make your allocator insert special guards to detect buffer overruns, keep accounting of allocations vs. deallocations to detect memory leaks, count various metrics for statistics and performance analysis, and much more.
  • Customization: for non-standard memory allocation schemes. One good example is pools or arenas for certain objects, which make memory management simpler. Another is a full-fledged garbage collection system for certain objects – this is all made possible by writing your custom operators new and delete for a class or a whole hierarchy.


Last Updated ( Tuesday, 29 March 2011 )