19.1.1. Overloading 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.


Image Warning

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.

The operator new and operator delete Interface

The 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 new12.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 new12.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.

The malloc and free Functions

If 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.


Exercises Section 19.1.1

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.


..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset