Memory leaks occur when an object has been created in the free store, but the program no longer has a way to access it. Because the object cannot be accessed, it cannot be deleted and so takes up mem- ory, even though it cannot be used. A program can quickly run out of memory and crash if it has sev- eral memory leaks. Even a single statement that is called multiple times can cause a memory leak.
Consider the following statements:
MagicBox<string>* myBoxPtr = new MagicBox<string>(); MagicBox<string>* yourBoxPtr = new MagicBox<string>(); yourBoxPtr = myBoxPtr; // Results in inaccessible object
Figure C2-6 traces the execution of these statements. Eventually, yourBoxPtr points to the same object asmyBoxPtr , and so the object to which yourBoxPtr originally pointed is no longer accessible, resulting in the memory leak shown in part c of the fi gure.
To prevent this leak, we should not create the second object. Instead, either initialize yourBoxPtr tonullptr or simply set it to myBoxPtr :
MagicBox<string>* myBoxPtr = new MagicBox<string>(); MagicBox<string>* yourBoxPtr = myBoxPtr;
A more subtle memory leak occurs when a function or method creates an object in the free store and loses the pointer to it by either not returning that pointer to the caller or not storing it in a class data member. That is what happens in the function given in Listing C2-1.
LISTING C2-1 Poorly written function that allocates memory in the free store
void myLeakyFunction( const double& someItem) {
ToyBox<double>* someBoxPtr = new ToyBox< double>(); someBoxPtr->setItem(someItem);
} // end myLeakyFunction
The parameter someItem and the pointer someBoxPtr are both stored in an activation record on the run-time stack and so are automatically destroyed after the function ends. The object created in the free store by new ToyBox<double>(RED) is still in the free store. Since the only reference we had to it was someBoxPtr and that has been destroyed, we no longer have a way to get to that object, and so we have a memory leak.
We have several options to fi x this function. The fi rst is to delete the object before the function terminates. We can do this by adding the lines
delete someBoxPtr; someBoxPtr = nullptr;
at the end of the function. If the ToyBox object pointed to by someBoxPtr is needed only by this func- tion, a better implementation choice would have been to not allocate memory from the free store, but to use a local variable such as is done in the following statements:
ToyBox< double> someBox(); // someBox is not a pointer variable someBox.setItem(someItem);
If the object created in this function is required outside the function, the function can return a pointer to it so that the caller can access the object. To implement this option, we would change the function’s return type from void to ToyBox<double>* and return someBoxPtr , which is a pointer to a ToyBox<double> object. The resulting function defi nition is
ToyBox< double>* pluggedLeakyFunction( const double& someItem) {
ToyBox< double>* someBoxPtr = new ToyBox<double>(); someBoxPtr->setItem(someItem);
return someBoxPtr;
} // end pluggedLeakyFunction
FIGURE C2-6 (a) Creating the fi rst object; (b) creating the second object; (c) assignment causes an inaccessible object
myBoxPtr
Object MagicBox<string>* myBoxPtr = new MagicBox<string>();
(a)
(c)
yourBoxPtr
Object MagicBox<string>* yourBoxPtr = new MagicBox<string>();
(b) yourBoxPtr = myBoxPtr; myBoxPtr Object yourBoxPtr Inaccessible object in free store (memory leak)
Pointers and the Program’s Free Store 125
To prevent a memory leak, do not use a function to return a pointer to a newly created object You could call this function by writing statements such as
double boxValue = 4.321;
ToyBox< double>* toyPtr = pluggedLeakyFunction(boxValue);
Now toyPtr points to the object created by the ToyBox constructor in pluggedLeakyFunction . When a function returns a pointer to an object that it created in the free store, the segment of the program using that pointer must take responsibility for deleting the object. Otherwise, a memory leak could still occur. When documenting such a function, you should add comments to indicate this responsibility. For example, you could precede the defi nition of pluggedLeakyFunction with these comments:
/** Creates an object in the free store and returns a pointer to it. Caller must delete the object when it is no longer needed. */
Of course, this comment does not prevent a memory leak, but it alerts users of your function about the potential for a leak. The function could still be misused and called with a statement such as
pluggedLeakyFunction(boxValue); // Misused; returned pointer is lost
in which case the returned pointer to the object in the free store is lost and a memory leak occurs. The best option for preventing a memory leak is to not use a function to return a pointer to a newly created object. Instead, you should defi ne a class that has a method for this task. The class takes responsibility for deleting the object in the free store and ensures that there is no memory leak. At a minimum, such a class will have three parts: a method that creates the object in the free store, a data fi eld that points to the object, and a method—the destructor — that deletes the object when the class instance is no longer needed. Although C++ Interlude 1 introduced class destructors, we must say more about them now.
Every C++ class has a destructor that has the same name as the class, but is preceded by the tilde (~) character. For objects that are local variables, the destructor is called when the activation record containing the object is removed from the run-time stack. If the object was created using new and is stored in the free store, the destructor is called when the client uses the operator delete to free the memory allocated to the object.
Often, the compiler-generated destructor is suffi cient for a class, but if the class itself creates an object on the free store by using the new operator, it is a safe and secure programming practice to implement a destructor to ensure that the memory for that object is freed.
Listing C2-2 gives the header fi le for a GoodMemory class that demonstrates how to avoid a mem- ory leak. The pointer variable in our previous function pluggedLeakyFunction is now a data fi eld in GoodMemory . As long as this fi eld points to an existing object in the free store, we will have access to the object.
LISTING C2-2 Header fi le for the classGoodMemory /** @file GoodMemory.h */ #include <string> #include "ToyBox.h" using namespace std; class GoodMemory { (continues)
private:
ToyBox<string>* someBoxPtr; public:
GoodMemory(); // Default constructor ~GoodMemory(); // Destructor
void fixedLeak(const double& someItem); }; // end GoodMemory
Because the class GoodMemory has a pointer variable for a data fi eld, we should defi ne a default constructor to initialize the pointer to nullptr . The destructor for this class simply needs to delete the object to which the pointer points. Listing C2-3 shows the implementation fi le for this class.
LISTING C2-3 Implementation fi le for the class GoodMemory
/** @file GoodMemory.cpp */ #include "GoodMemory.h"
GoodMemory::GoodMemory() : someBoxPtr( nullptr) {
} // end default constructor GoodMemory::~GoodMemory() {
delete someBoxPtr; } // end destructor
void GoodMemory::fixedLeak(const double& someItem) {
someBoxPtr = new ToyBox< double>(); someBoxPtr->setItem(someItem); } // end fixedLeak
Unlike the original function myLeakyFunction , as presented in Listing C2-1, the following client function uses the classGoodMemory to guarantee that no memory leak occurs:
void goodFunction() {
double boxValue = 4.321; // Original statement
GoodMemory gmObject; // Create a safe memory object gmObject.fixedLeak(boxValue); // Perform the task
} // end goodFunction
The compiler greatly helps us here. First, the compiler can check to ensure that a GoodMemory object has been created before calling the methodfixedLeak . This guarantees a safe memory alloca- tion and deallocation. Then, since the variable gmObject is a local variable, the compiler automati- cally calls the GoodMemory destructor when execution of the function goodFunction ends. Thus, the memory allocated in the free store is freed.
Pointers and the Program’s Free Store 127