Introduction
It is an interesting question to confuse someone that how we can make an array which has different data types or more appropriately array of objects of different classes. The answer of some clever programmer is to make one base class of all those different classes and make array of pointer of that base class, then create the appropriate object and store its address in base class pointer in array. And may be a more smart programmer suggests to make vector of base class pointer instead of array. Before going to create 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].
Ok you agree to delete all elements, whose pointers are stored in container, by using simple loop or making predicate and using for
_each
. But there is still one problem you have to keep in your mind that the destructor of your base class has to be "Virtual" because if you never do this, then the destructor of drive class won't be called. Well if you have decided to make virtual destructor in base class, then your code to do all this is something like this:
std::vector<Base*> vecBase;
for (std::vector<Base*>::iterator iter_ =
vecBase.begin();
iter_ != vecBase.end();
++iter_
{
delete *iter_;
}
Even if you decide to make virtual destructor of your base class then there is still a case in which this solution is not good and that is point to STL container classes such as string. Because no STL classes have virtual destructor therefore you can't apply the same thing with STL container. This code clearly has resource leak if you inherit the class from std::string
and store its address, even if you delete all elements.
std::vector<std::string*> vecString;
Ok we agree to make virtual destructor and not using the pointer to container of STL container classes. But there is still one problem in this solution and that is exception safety. Suppose due to any reason, exception is thrown during the deletion loop or before deletion loop, then all elements are not deleted. The smartest solution, which comes in mind in case of dynamically created objects and exception safety, is smart pointer i.e. auto_ptr
. So the solution is container of auto_ptr
(COAP), but wait COAP is not a portable solution and this code shouldn't compile. Before discussing it in detail, lets take a quick review of auto_ptr
first.
What is auto_ptr
? Before discussing what is auto_ptr
, take a look at the problem which auto_ptr
solves? Take a look at this simple piece of code in which we allocate some object and do some processing on it before deleting it.
int* pInt = new int;
delete pInt;
Well the code is quite straight forward and doesn't have any problem in normal flow. But what happens if exception will be thrown before deleting the object? It is clearly resource leak. From here auto_ptr
comes in action. It is just a template class which allocates object, gives pointer like interface and clear the allocated object in its destructor. Now our code becomes something like this:
std::auto_ptr<int> ptInt(new int);
Now there is no need to call delete, because smart pointer is responsible to do it when it goes out of scope, whether the flow of code is normal or exception has been thrown. Item 68 of C++ Gotchas "Improper Use of auto_ptr" [DEW02] discusses this issue too as a common C++ Gotchas. The most common misuse of auto_ptr
is to allocate array of objects rather than just one.
std::auto_ptr<int> ptIntArr (new int[iLen]);
The best alternate solution is to use vector instead of dynamically allocated array itself. But if you are still interested to use auto_ptr
, then take a look at 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 transfer semantic when you copy it. In other words when you copy auto_ptr
then you will automatically transfer the ownership of the object from source to target and source auto_ptr
becomes NULL
. This is a reason why copy constructor and assignment operator of auto_ptr
doesn't have const reference of same object, just like other copy constructors and assignment operators.
template<typename X>
class auto_ptr
{
explicit auto_ptr(X* p = 0) throw();
auto_ptr (auto_ptr&) throw();
auto_ptr& operator = (auto_ptr&) throw();
}
But what's the need of this semantic? Just suppose for a moment that auto_ptr
doesn't have this semantic and after copy both source and target holds the same object, then take a look at the following code:
void f()
{
auto_ptr<int> ptInt1(new Int);
auto_ptr<int> ptInt2 (pInt1);
auto_ptr<int> ptInt3;
ptInt3 = pInt2;
}
Now if both source and target holds the same objects, then when function's finished and pInt1
, pInt2
and pInt3
goes out of scope, destructor of all objects called. And each destructor, except the first one, tries to delete the same object, i.e. already deleted object. But fortunately auto_ptr
has 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 true copy in case of auto_ptr
, i.e. source and objects are not same after copy operation. If you don't want to change the ownership of auto_ptr
then 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 come to STL container. All STL containers have copy semantic not reference semantic, means when you insert anything in any STL container then its copy is created and put in the container and the same thing apply when you get something from container. For further information about copy semantic, take a look at Item 3 of Effective STL [MEY01]. But unfortunately the auto_ptr
semantic doesn't fulfill the copy requirement of STL container, therefore it is not recommended to use it in container. If you try to do it then you may get unpredictable result, such as some or all of your value of container becomes NULL
due to ownership transfer semantic, because we don't know in advance what is the internal working of a particular algorithm.
References
- [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