People are often confused about how we can make an array that has different data types or, more appropriately, an array of objects of different classes. Some clever programmers make one base class of all those different classes, make an array of pointers of that base class, and then create the appropriate object and store its address in the base class pointer in the array. And, maybe a smart programmer suggests making a vector of the base class pointer instead of an array. Before creating this, take a look at Scott Meyers’s advice in “Effective STL” item 7, i.e. “When using containers of newed pointers, remember to delete the pointers before the containers is destroyed” [MEY01].
Okay; you agree to delete all elements whoses pointer are stored in containers, by using a simple loop or making a predicate and use for_each. But, there is still one problem you have to keep in mind: that the destructor of your base class has to be “Virtual” because, if you never do this, the destructor of the drive class won’t be called. Well, you have decide to make the virtual destructor in the base class. Then, your code to do all this is something like this:
// initialize it with the appropriate drive class object
// now delete all new objects
for (std::vector<Base*>::iterator iter_ = vecBase.begin();
iter_ != vecBase.end();
Even if you decide to make a virtual destructor of your base class, there is still a case in which this solution is not good. That is, you can point to STL container classes such as strings. No STL classes have a virtual destructor; therefore, you can’t apply the same thing with an STL container. This code clearly has a resource leak if you inherit the class from std::string and store its address, even if you delete all elements.
Okay; we agree to make a virtual destructor and not use the pointer to the container of the STL container classes. But, there is still one problem in this solution; that is exception safety. Suppose, due to any reason, an exception is thrown during the deletion loop or the before deletion loop; then all elements are not deleted. The smartest solution that comes to mind in case of dynamically created objects and exception safety, is a smart pointer—auto_ptr. So, the solution is a container of auto_ptr (COAP), but wait. COAP is not a portable solution and this code shouldn’t compile. Before discussing it in detail, let’s take a quick review of auto_ptr first.
What is auto_ptr? Before we discuss what auto_ptr is, take a look at a problem that auto_ptr solves. Look at this simple piece of code in which we allocate some object and do some processing on it before delete it.
int* pInt = new int;
// do some work on pInt
Well, the code is quite straightforward and doesn’t have any problem in normal flow. But what happens if an exception is thrown before we delete the object? It is clearly a resource leak. From here, auto_ptr comes in action. It is just a template class that allocates an object, gives a pointer like an interface, and clears the allocated object in its destructor. Now, our code becomes something like this:
std::auto_ptr<int> ptInt(new int);
// do work on ptInt and no need to call delete;
Now, there is no need to call delete because a smart pointer is responsible for doing it when it goes out of scope, whether the flow of code is normal or an exception has been thrown. Item 68 of C++ Gotchas’ “Improper Use of auto_ptr” [DEW02] discusses this issue too as a common C++ Gotcha. The most common misuse of auto_ptr is to allocate an array of objects rather than just one.
std::auto_ptr<int> ptIntArr (new int[iLen]);
The best alternate solution is to use a vector instead of dynamically allocating the array itself. But, if you are still interested in using auto_ptr, take a look at the adapter pattern technique discussed in Item 29 of More Exceptional C++ [SUT01], “Using auto_ptr”.
So, what’s wrong with auto_ptr? auto_ptr holds the ownership of the object, which pointers it holds. But, auto_ptr has ownership of the transfer semantic when you copy it. In other words, when you copy auto_ptr, you will automatically transfer the ownership of the object from the source to the target and the source auto_ptr becomes NULL. This is a reason that the copy constructor and assignment operator of auto_ptr don’t have a const reference of the same object, just like other copy constructors and assignment operators.
explicit auto_ptr(X* p = 0) throw();
// don’t not have const reference as parameter
auto_ptr (auto_ptr&) throw();
auto_ptr& operator = (auto_ptr&) throw();
// template version of copy constructor and assignment operator
// other functions
But, what’s the need of this semantic? Just suppose for a moment that auto_ptr doesn’t have this semantic and, after copying both the source and target, holds the same object. Take a look at the following code.
auto_ptr<int> ptInt1(new Int);
// copy constructor
auto_ptr<int> ptInt2 (pInt1);
// assignment operator
ptInt3 = pInt2;
Now, if both the source and target hold the same objects, when the functions are finished and pInt1, pInt2, and pInt3 go out of scope, the destructor of all objects is called. And, each destructor, except the first one, tries to delete the same object; in other words, the already deleted object. But fortunately, auto_ptr has an transfer ownership semantic; therefore, in this example ptInt1 and ptInt2 are NULL and it is safe to delete NULL. In other words, it is not a true copy in the case of auto_ptr—the source and objects are not same after the copy operation. If you don’t want to change the ownership of auto_ptr, make it constant. But, of course, there are other limitations of constants too. For further information, take a look at Item 37 “auto_ptr” of Exceptional C++ [SUT00].
Now, we come to the STL container. All STL containers have a copy semantic, not a reference semantic. This means that when you insert anything in any STL container, its copy is created and put in the container. The same thing applies when you get something from the container. For further information about copy semantics, take a look at Item 3 of Effective STL [MEY01]. But unfortunately, the auto_ptr semantic doesn’t fulfill the copy requirement of the STL container; therefore, it is not recommended to use it in a container. If you try to do it, you may get unpredictable result, such as some or all of your value of container becomes NULL due to the ownership transfer semantic, because we don’t know in advance what the internal working of a particular algorithm is.
- [DEW02] C++ Gotchas, Avoiding Common Problems in Coding and Design, Stephen C. Dewhurst, 2002.
- [MEY01] Effective STL, Scott Meyers, 2001.
- [SUT00] Exceptional C++ 47 Engineering puzzles, programming problems and solutions, Herb Sutter, 2000.
- [SUT01] More Exceptional C++ 40 New Engineering puzzles, programming problems and solutions, Herb Sutter, 2001.