Chapter 12. New and Delete

FAQ 12.01 Does new do more than allocate memory?

Yes, it also initializes the new object.

Assuming Fred is a known type, the expression new Fred() is a two-step operation. The first step is to allocate sizeof(Fred) bytes of memory using a memory allocator primitive called operator new(size_t nbytes) (size_t is a typedef for an unsigned integral type such as unsigned int). This memory allocation primitive is conceptually similar to but not interchangeable with malloc(size_t nbytes). The second step is to call the appropriate constructor of the class (Fred::Fred() in this case).

Similarly, delete p is a two-step operation: it first calls the destructor on the object *p, then it releases the memory pointed to by p using a memory deallocation primitive. This memory deallocation primitive is called operator delete(void* p) and is conceptually similar to but not interchangeable with free(void* p).

FAQ 12.02 Why is new better than good old trustworthy malloc()?

image

It does more.

Object construction: In C++, new and delete create and destroy objects. In contrast, malloc() and free() merely allocate and deallocate memory.

Safety: The new operator returns a pointer of the correct type whereas the function malloc() returns a void*, which isn't type safe. The C language allows a void* to be converted to any other pointer, but this is a dangerous hole in C's type-checking system. C++ doesn't have this weakness: converting a void* to a different pointer type requires an explicit cast in C++.

Flexibility: The new operator can be overloaded by a class. For example, new Fred() can use a different memory allocation primitive than is used by new Wilma(). In contrast, malloc() cannot be overloaded on a class-by-class basis.

FAQ 12.03 Does C++ have a counterpart to realloc() that goes along with new and delete?

image

No; and don't use realloc() directly, since bitwise copying of an object of a user-defined class is evil.

When realloc() needs to move data during the reallocation, it uses a bitwise copy, which is disastrous for many user-defined classes (see FAQ 30.15). C++ objects know how to copy themselves using their own copy constructors and assignment operators.

Never use realloc() on objects of user-defined classes. Let the objects copy themselves. Better yet, use the vector template class rather than pointers to arrays, and the vector template will take care of reallocation automatically and correctly (see FAQ 28.13).

FAQ 12.04 Can pointers returned from new be deallocated with free()? Can pointers returned from malloc() be deallocated with delete?

No!

It is perfectly legal, moral, and wholesome to use malloc() and delete in the same program or to use new and free() in the same program. But it is illegal, immoral, and despicable to call free() on a pointer allocated via new or to call delete on a pointer allocated via malloc().

Even if it appears to work on your particular compiler on your particular machine, please don't do it. Corrupting the heap is a very subtle and disastrous thing; it's just not worth the trouble—even if the data type is a simple array of char; even if some programmers think it would be cool. Just say no.

FAQ 12.05 Does delete p delete the pointer p or the referent *p?

The referent *p.

If verbosity were a virtue, the syntax would be changed from delete p to deleteTheThingPointedToBy p. One could argue that the current syntax is misleading, but the same abuse of English occurs with free(p) from the C language: free(p) doesn't free p; rather it frees the memory pointed to by p.

FAQ 12.06 Should the pointer returned from new Fred() be checked to see if it is NULL?

image

No, new Fred() never ever returns NULL. Instead, if new runs out of memory during new Fred(), it throws an exception of type bad_alloc (see FAQ 9.02).

Because of this, the if test in the following example is considered bad form since it increases code size, increases code complexity, and increases testing costs, yet it adds no value (remember, exceptions guarantee that p will never be NULL).

image

C programmers please note that this behavior is very different from the way out-of-memory is handled by malloc(). To be safe, every call to malloc() has to be followed by an explicict if test to see if malloc() returned NULL.

FAQ 12.07 How can new be convinced to return NULL rather than throw an exception?

image

Replace new Fred() with new(nothrow) Fred(). Note that this is a step backward for the reasons already described, so this technique should not be extensively used in new C++ code. However this technique is relevant for people who have to deal with C++ software that was written before new Fred() threw exceptions.

When preexception C++ code is compiled on a modern C++ compiler, the use of exceptions may cause the application to crash. In this case, there are two basic options: update the application by making it exception safe (that is, make the code do something reasonable even if something such as new throws an exception), or patch the application by replacing new Fred() with new(nothrow) Fred(). Often, but not always, it is cheaper to patch it rather than make it exception safe. The following example demonstrates this technique.

image

FAQ 12.08 How can new be set up to automatically flush pools of recycled objects whenever memory runs low?

image

Applications that do a lot of freestore allocations can sometimes improve performance by using global pools of recycled objects. For example, when a dynamically allocated Fred object is no longer needed, the programmer can say p->discard() rather than delete p, and the discard() member function adds the object to a static pool of Fred objects. Then when a Fred object is needed, the programmer says Fred::create() rather than new Fred(), and the static create() member function returns the first entry from the list (or returns new Fred() if the list is empty).

Everything works great until memory runs low, at which point the pool of Fred objects needs to be flushed to free up available memory. It would be ideal if the runtime system would automatically call some routine such as Fred::flushPool() whenever new ran low on memory, since the pool could be flushed without any functional impact. For example, if someone wants to create a Wilma object and the system runs out of memory because there are too many recycled Fred objects in the Fred pool, the goal is to have the system automatically call Fred::flushPool(), which actually deletes all the Fred objects on the recycled list. We set up the Fred class with its pool of recycled objects:

image

image

First, notice how users are prevented from saying new Fred() or delete p. Instead users must say Fred::create() and p->discard(), respectively. The discard() member function adds the object to the recycled pool, and the create() static member function uses the recycled pool if it isn't empty. Finally the flushPool() static member function flushes the pool of recycled Fred objects, and returns a bool indicating whether anything actually was deleted.

Next, to acomplish the larger goal of having the system automatically call Fred::flushPool() whenever new runs out of memory, a special function is created that calls Fred::flushPool() (and possibly other similar pools, such as Wilma::flushPool()). This special function is known as a new handler and is called flushAllPools() in the following example. If operator new(size_t nbytes) runs out of memory, it calls this function, which tries to delete some unneeded memory. If the new handler succeeds at freeing up some storage, it simply returns to operator new(size_t nbytes), and operator new(size_t nbytes) tries the allocation again. If the new handler is unsuccessful at freeing up storage, it avoids an infinite loop by throwing an exception:

image

The final step is to register the function flushAllPools() as the official new handler. This is done using the set_new_handler() function and is normally called very early in the application's execution:

image

The rest is automatic: if someone says new Barney() and the underlying allocator (operator new(size_t nbytes)) runs out of memory, the allocator automatically calls the new handler (flushAllPools()), which flushes the Fred pool (Fred::flushPool()). If something actually was flushed, the new handler returns to operator new(size_t), which tries again. If it fails a second time, the whole process repeats. Eventually one of two things happens: either operator new(size_t) succeeds, in which case the caller who said new Barney() will never know that any of this ever happened, or flushAllPools() fails to flush anything and throws an exception (in which case the new Barney() attempt vanishes, and control goes to the appropriate catch handler; see FAQ 9.03). In either case, the users who are saying new Barney() don't know anything about the pool mechanism—it is invisible to them.

FAQ 12.09 What happens if delete p is called when p is NULL?

Nothing.

Calling delete p when p is NULL is safe and is guaranteed to do nothing. This simplifies code that uses delete by letting programmers say delete p; rather than if (p != NULL) delete p;. For example,

image

There are two problems with the explicit if test: first, some people get the test backwards (e.g., they say if (!p) delete p; which is backward), and second, if tests significantly increase the cost of testing—to achieve branch point coverage, both the “if true” and the “if false” branches of every if need to be exercised. Thus, adding unnecessary branch points, such as if statements, to an application causes the creation of unnecessary test cases. Conversely, if a branch point can be removed from the software without complicating or invalidating something else, in general the expected quality of the software goes up and the testing cost goes down.

FAQ 12.10 What happens when a pointer is deleted twice?

Catastrophe.

Suppose there is a pointer variable p. The first time delete p is executed, the object *p is safely destructed and the memory pointed to by p is safely returned to the heap. The second time the same pointer is passed to delete without a subsequent new that returned that pointer, the remains of what used to be an object at *p are passed to the destructor (which could be disastrous), and the memory pointed to by p is handed back to the heap a second time. This is likely to corrupt the heap and its list of free memory. The following example illustrates this situation.

image

FAQ 12.11 How can an array of things be allocated and deallocated?

image

The best way is to be radical. Instead of trying to use an array pointer correctly, it is easier (and often more efficient) not to use explicit pointers at all but instead to use a container template such as vector (see FAQ 28.13). Please don't use explicit pointers unless it is necessary. Pointers are a source of a lot of errors; using a good container class library can eliminate many pointer errors.

If it is necessary or desired to use pointers anyway, the right way to allocate an array of things is with p = new Fred[n]. When the array is deallocated, the [] must appear just after the delete keyword, such as delete[] p;. Here is an example.

image

The purpose of the syntactic difference between delete p and delete[] p is to distinguish deleting a thing from deleting an array of things. This is because there is no syntactic difference between the type “pointer to a thing” (Fred*) and the type “pointer to the first element of an array of things” (Fred*). This is a feature that C++ inherited from C.

After all this, recall that the real solution is to not use pointers at all but instead to use a good container class library. For example, when an array of things is needed, use a container class that implements an array of things, such as the standard template vector (see FAQ 28.13).

FAQ 12.12 What if delete p (not delete[] p) is used to delete an array allocated via new Fred[n]?

Catastrophe.

It is the programmer's responsibility—not the compiler's—to verify that the connection between new[] and delete[] is correct. If it is wrong, don't expect either a compiler error message or a clean runtime exception. Expect a disaster. Worse, the disaster might not show up during testing; it might not show up until after the software is in the field.

For example, some implementations immediately corrupt the heap when the [] is omitted when deleting an array of objects; other implementations fail to destruct all but the first object in the array. The latter could cause memory leaks if the destructors release memory, cause deadlock if the destructors unlock semaphores, compromise system integrity in other ways, or trigger some combination of any or all of these.

Remember that this headache can be instantly eliminated if container classes, such as vector, are used instead of raw pointers. Please use raw pointers only when it's absolutely necessary.

FAQ 12.13 Can the [] of delete[] p be dropped when p points to an array of some built-in type such as char?

No; there is no reason to do this and it risks an avoidable disaster.

Some programmers tragically think that the [] in the delete[] p exists only so that the compiler will call the appropriate number of destructors. Following this reasoning, they assume that the [] are optional when the array is of some built-in type such as an array of char:

image

The delete p; line above is wrong, and it can cause a disaster at runtime. In particular, the underlying deallocation primitive called for delete p; is operator delete(void*), but the deallocation primitive called for delete[] p; is operator delete[](void*). The default behavior for the latter is to call the former, but users are allowed to replace the latter with a different behavior. For example, someone might replace operator new[](size_t) (the allocation primitive called for new char[n]) and operator delete[](void*) with a separate heap from operator new(size_t) and operator delete(void*). If that happens, the delete p; line sends the pointer to the wrong heap, which could result in a disaster at runtime.

Remember: use container classes rather than raw pointers. This example could use the standard string class or perhaps something like the standard vector template.

FAQ 12.14 How is an object constructed at a predetermined position in memory?

With the placement syntax of the new operator, also known as placement new.

Objects are normally created on the stack, on the heap, or in static memory. These correspond to automatic allocation, dynamic allocation, and static allocation, respectively. But these normal techniques don't allow the programmer to specify the exact address at which the object will live.

Occasionally an object's desired location is known before the object is created, such as when the hardware uses a piece of storage as a way of communicating with the software. In these cases, placement new can be used.

The following example places an object of class Fred at the hexadecimal address 0xFEEDBABE and passes (42, 42) to the Fred constructor.

image

The storage pointed to by place must be large enough to hold sizeof(Fred) bytes and must be properly aligned to hold a Fred object. The returned pointer p is numerically the same as place, but p is a Fred* rather than a void*.

FAQ 12.15 How can class Fred guarantee that Fred objects are created only with new and not on the stack?

image

The class can make all of its constructors private: or protected: and can provide static create() member functions. The copy constructor should also be made private: or protected:, even if it doesn't need to be defined otherwise (see FAQ 30.06). The static (or friend) create() functions then create the object using new and return a pointer to the allocated object. Here's an example.

image

Note that derived classes can't be instantiated since all of the constructors are private:. Derived classes could be instantiated only if some of the constructors were protected:.

FAQ 12.16 How are objects created by placement new destroyed?

By explicitly calling the object's destructor. This is about the only time a destructor is called explicitly (see FAQ 20.10). For example, if p is a Fred* that was returned from placement new, *p can be destructed as follows.

image

Caution: Do not explicitly call the destructor of an object that will later be automatically destroyed, such as an object on the stack, an object on the heap that will be deleted, or a static object. The only time a destructor should be called explictly is when the programmer is in total control of the storage allocation and lifetime of the object—in other words, only with objects initialized by the placement new syntax.

FAQ 12.17 In p = new Fred(), does the Fred memory “leak” if the Fred constructor throws an exception?

image

No, the system straightens things out automatically.

If an exception occurs in the Fred constructor during p = new Fred(), the sizeof(Fred) bytes that were allocated are automatically released back to the heap. This is because new Fred() is a two-step process.

  1. sizeof(Fred) bytes of memory are allocated using the memory allocation primitive void* operator new(size_t nbytes). This primitive is similar in spirit to malloc(size_t nbytes) (however operator new(size_t) and malloc(size_t) are not interchangeable; the two memory allocation primitives may not even use the same heap!). Recall that size_t is a typedef for some unsigned integral type such as unsigned int. Many system headers cause this typedef to be defined.
  2. A Fred object is constructed in the returned memory location by calling the Fred constructor. Thus the pointer returned from the first step is passed as the constructor's this parameter. The call to the constructor is conceptually wrapped in a try block so that the memory can be released if the constructor throws an exception.

Thus the compiler generates code that looks something like that shown in following function sample().

image

The statement new(p) Fred(); is called the placement new syntax (see FAQ 12.14). The effect is to call the Fred constructor, passing the pointer p as the constructor's this parameter.

FAQ 12.18 Is it legal (and moral) for a member function to say delete this?

Yes, but be careful.

Programmers usually have a hard time emotionally accepting that this is valid, probably because it seems as if the member function is inside the object and deleting the object during a member function seems strange. But with care, this technique can be perfectly safe. Here is how we define “with care.”

  1. The this object must have been allocated via new (see FAQ 12.15), not by new[] (see FAQ 12.11) nor by placement new (see FAQ 12.14) nor by a local object on the stack nor by a global nor by a member of another object. It has to have been allocated by plain, ordinary new.
  2. The member function that contains delete this; must be the last member function that is invoked on the this object.
  3. The remainder of the member function after the delete this; line must not touch any piece of the this object, including calling any other member functions or touching any data members.
  4. No other piece of code should even examine the this pointer itself after the delete this; line. No one may examine it, compare it with another pointer, compare it with NULL, print it, cast it, do anything with it.
  5. Make sure no one else does a delete on the object. For example, if the object is still being held by an auto_ptr (which would be a good thing!), the release() member function must be called on the auto_ptr; otherwise the auto_ptr will delete the object again, which would be a disaster. For example:

    image

FAQ 12.19 After p = new Fred[n], how does the compiler know that there are n objects to be destructed during delete[] p?

Warning: This FAQ is quite low level. The only people who really need to worry about this level of detail are those in extremely performance sensitive situations where CPU cycles are at a premium. Of course it also might be interesting to those who are just plain curious...

Whenever someone says Fred* p = new Fred[n], the runtime system is required to store the number of objects, n, in a place that can be retrieved knowing only the pointer, p. The compiler can use any technique it wants to use, but there are two popular ones.

  1. The code generated by p = new Fred[n] might store the number n in a static associative array, where the pointer p is used as the lookup key and the number n is the associated value. For example, using the standard map template (see FAQ 28.14), this associative array might be map<void*,size_t>. The code generated by delete[] p would look up the pointer in the associative array, would extract the associated size_t, then would remove the entry from the associative array.
  2. The code generated by p = new Fred[n] might allocate an extra sizeof(size_t) bytes of memory (possibly plus some alignment bytes) and put the value n just before the first Fred object. Then delete[] p would find n by looking at the fixed offset before the first Fred object (that is, before *p) and would deallocate the memory starting at the beginning of the allocation (that is, the block of memory beginning the fixed offset before *p).

Neither technique is perfect. Here are a few of the tradeoffs.

  1. The associative array technique is slower but safer: if someone forgets the [] when deallocating an array of things, (a) the entry in the associative array would be a leak, and (b) only the first object in the array would be destructed. This may or may not be a serious problem, but at least it might not crash the application.
  2. The overallocation technique is faster but more dangerous: if someone says delete p where they should have said delete[] p, the address that is passed to operator delete(void* p) would not be a valid heap allocation—it would be at least sizeof(size_t) bytes after a valid heap allocation. This would probably corrupt the heap. Bang, you're dead.
..................Content has been hidden....................

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