new
and delete
Although we say that we can “overload new
and delete
,” overloading these operators is quite different from the way we overload other operators. In order to understand how we overload these operators, we first need to know a bit more about how new
and delete
expressions work.
When we use a new
expression:
// new expressions
string *sp = new string("a value"); // allocate and initialize a string
string *arr = new string[10]; // allocate ten default initialized strings
three steps actually happen. First, the expression calls a library function named operator new
(or operator new[]
). This function allocates raw, untyped memory large enough to hold an object (or an array of objects) of the specified type. Next, the compiler runs the appropriate constructor to construct the object(s) from the specified initializers. Finally, a pointer to the newly allocated and constructed object is returned.
When we use a delete
expression to delete a dynamically allocated object:
delete sp; // destroy *sp and free the memory to which sp points
delete [] arr; // destroy the elements in the array and free the memory
two steps happen. First, the appropriate destructor is run on the object to which sp
points or on the elements in the array to which arr
points. Next, the compiler frees the memory by calling a library function named operator delete
or operator delete[]
, respectively.
Applications that want to take control of memory allocation define their own versions of the operator new
and operator delete
functions. Even though the library contains definitions for these functions, we can define our own versions of them and the compiler won’t complain about duplicate definitions. Instead, the compiler will use our version in place of the one defined by the library.
When we define the global operator new
and operator delete
functions, we take over responsibility for all dynamic memory allocation. These functions must be correct: They form a vital part of all processing in the program.
Applications can define operator new
and operator delete
functions in the global scope and/or as member functions. When the compiler sees a new
or delete
expression, it looks for the corresponding operator
function to call. If the object being allocated (deallocated) has class type, the compiler first looks in the scope of the class, including any base classes. If the class has a member operator new
or operator delete
, that function is used by the new
or delete
expression. Otherwise, the compiler looks for a matching function in the global scope. If the compiler finds a user-defined version, it uses that function to execute the new
or delete
expression. Otherwise, the standard library version is used.
We can use the scope operator to force a new
or delete
expression to bypass a class-specific function and use the one from the global scope. For example, ::new
will look only in the global scope for a matching operator new
function. Similarly for ::delete
.
operator new
and operator delete
InterfaceThe library defines eight overloaded versions of operator new
and delete
functions. The first four support the versions of new
that can throw a bad_alloc
exception. The next four support nonthrowing versions of new
:
// these versions might throw an exception
void *operator new(size_t); // allocate an object
void *operator new[](size_t); // allocate an array
void *operator delete(void*) noexcept; // free an object
void *operator delete[](void*) noexcept; // free an array
// versions that promise not to throw; see § 12.1.2 (p. 460)
void *operator new(size_t, nothrow_t&) noexcept;
void *operator new[](size_t, nothrow_t&) noexcept;
void *operator delete(void*, nothrow_t&) noexcept;
void *operator delete[](void*, nothrow_t&) noexcept;
The type nothrow_t
is a struct
defined in the new
header. This type has no members. The new
header also defines a const
object named nothrow
, which users can pass to signal they want the nonthrowing version of new
(§ 12.1.2, p. 460). Like destructors, an operator delete
must not throw an exception (§ 18.1.1, p. 774). When we overload these operators, we must specify that they will not throw, which we do through the noexcept
exception specifier (§ 18.1.4, p. 779).
An application can define its own version of any of these functions. If it does so, it must define these functions in the global scope or as members of a class. When defined as members of a class, these operator functions are implicitly static (§ 7.6, p. 302). There is no need to declare them static
explicitly, although it is legal to do so. The member new
and delete
functions must be static because they are used either before the object is constructed (operator new)
or after it has been destroyed (operator delete)
. There are, therefore, no member data for these functions to manipulate.
An operator new
or operator new[]
function must have a return type of void*
and its first parameter must have type size_t
. That parameter may not have a default argument. The operator new
function is used when we allocate an object; operator new[]
is called when we allocate an array. When the compiler calls operator new
, it initializes the size_t
parameter with the number of bytes required to hold an object of the specified type; when it calls operator new[]
, it passes the number of bytes required to store an array of the given number of elements.
When we define our own operator new
function, we can define additional parameters. A new
expression that uses such functions must use the placement form of new
(§ 12.1.2, p. 460) to pass arguments to these additional parameters. Although generally we may define our version of operator new
to have whatever parameters are needed, we may not define a function with the following form:
void *operator new(size_t, void*); // this version may not be redefined
This specific form is reserved for use by the library and may not be redefined.
An operator delete
or operator delete[]
function must have a void
return type and a first parameter of type void*
. Executing a delete
expression calls the appropriate operator
function and initializes its void*
parameter with a pointer to the memory to free.
When operator delete
or operator delete[]
is defined as a class member, the function may have a second parameter of type size_t
. If present, the additional parameter is initialized with the size in bytes of the object addressed by the first parameter. The size_t
parameter is used when we delete objects that are part of an inheritance hierarchy. If the base class has a virtual destructor (§ 15.7.1, p. 622), then the size passed to operator delete
will vary depending on the dynamic type of the object to which the deleted pointer points. Moreover, the version of the operator delete
function that is run will be the one from the dynamic type of the object.
malloc
and free
FunctionsIf you define your own global operator new
and operator delete
, those functions must allocate and deallocate memory somehow. Even if you define these functions in order to use a specialized memory allocator, it can still be useful for testing purposes to be able to allocate memory similarly to how the implementation normally does so.
To this end, we can use functions named malloc
and free
that C++ inherits from C. These functions, are defined in cstdlib
.
The malloc
function takes a size_t
that says how many bytes to allocate. It returns a pointer to the memory that it allocated, or 0 if it was unable to allocate the memory. The free
function takes a void*
that is a copy of a pointer that was returned from malloc
and returns the associated memory to the system. Calling free(0)
has no effect.
A simple way to write operator new
and operator delete
is as follows:
void *operator new(size_t size) {
if (void *mem = malloc(size))
return mem;
else
throw bad_alloc();
}
void operator delete(void *mem) noexcept { free(mem); }
and similarly for the other versions of operator new
and operator delete
.
Exercise 19.1: Write your own operator new(size_t)
function using malloc
and use free
to write the operator delete(void*)
function.
Exercise 19.2: By default, the allocator
class uses operator new
to obtain storage and operator delete
to free it. Recompile and rerun your StrVec
programs (§ 13.5, p. 526) using your versions of the functions from the previous exercise.