Originally posted by grib
use std::vector<ABC *> v;
to add a Derived1 v.push_back(new Derived1(/*args if needed*/));
any calls on methods of ABC will call the Derived method iff those methods are virtual.
If you need to use something that's not in the ABC you need to use dynamic_cast<Derived1 *>(v[3]); This will give you a pointer iff v[3] actually points to a Dervived1.
Naturally, you need to rember to delete each element.
I agree with the std::vector idea, but I'd take it a step further. Download a good supplimentary library, like Boost and use a vector of shared_ptr instead. Unlike std::auto_ptr, boost::shared_ptr is container-safe (and will almost certainly be included in the next revision of the standard library).
The benefit here is that you never have to manually delete any pointers, and you get exception safety (as long as you don't try to instantiate a pointer as a temporary in a function call, you can't leak memory even if an exception is thrown).
Arrays and pointers are the two most dangerous features of the language; they're where your memory leaks will occur. "When you learn C, you spend most of the time learning how to use arrays and pointers; when you learn C++, you spent most of the time learning how to avoid using them." This is not to suggest that you forget about learning pointers & arrays, but rather, that you learn them, and then learn their drawbacks as well.
It is not easy to try to make allocating objects within an array of object pointers exception-safe (e.g. if you are allocating 100 objects, and object 53 throws an exception at creation, you need to destroy objects 1-52).
For example, this is NOT exception safe:
Code:
// Assume MyObject** array is a member of MyClass
MyClass::MyClass(){
array = new MyObject* [100]; // Line A
for (int i = 0; i < 100; i++)
array[i] = new MyObject; // Line B
}
Now suppose an exception occurs at Line B when i = 50. What happens?
1) ~MyClass() will NOT be called, because the constructor didn't finish.
2) The *variable* array (which is really a pointer to the first element of an array of pointers) is freed.
3) The memory allocated in Line A (the array of pointers itself) is NOT freed.
4) The 50 objects that did get constructed in Line B are not destroyed, and their memory is not freed.
In this example, you leak 50*sizeof(MyObject) + 100 * sizeof(MyObject *), because you allocated 50 objects and 100 pointers that can't be freed.
Note, it is certainly possible to make it exception safe, by catching the exception immediately, deleting all the objects that were successfully made, and then rethrowing the exception (assuming you want to abort the constructor when you can't allocate). But this can take up a lot of code -- the cleanup code would take up more space than the allocation code! Further, there's a more elegant way of achieving the same goal. The elegant way also has the benefit that it can't leak resources for other reasons -- e.g. a malformed destructor for the above method could also be a source of a resource leak, or a member function that did array operations, etc. You can think of it as plugging many possible leaks at once.
The elegant way would be:
Code:
typedef boost::shared_ptr<MyObject> MyObjectPtr;
typedef std::vector<MyObjectPtr> MyObjectVector;
// assume MyObjectVector mov is a member of MyClass
MyClass::MyClass(){
mov.reserve(100); // Line A
for (int i = 0; i < 100; i++)
mov.push_back(MyObjectPtr(new MyObject)); // Line B
}
Assume the exception happens just as before. Now what happens?
1) ~MyClass() will still not be called, because the constructor didn't finish. (which is good; destroying a half-built object is not a good idea)
2) The variable mov is freed. Because mov itself finished construction, it is destroyed (its destructor is called and its memory is released).
3) As mov is destroyed, each of the 50 smart pointers that constructed properly is deleted by vector's destructor.
4) The memory (potentially) allocated in Line A is freed with the destruction of the vector object.
5) When each of the smart pointers is deleted, its reference count drops to zero (it was the last pointer to that object) so it frees the memory of the 50 MyObject objects.
Now, you have the same functionality as before, but you are protected against memory leaks, even when an exception is thrown partway through construction.
This also has the added benefit that both the vector and the pointers "clean up after themselves", so you don't need to remember to destroy them.
Do note, that your objects (of class MyObject) MUST NEVER throw an exception in their destructors. This is a general rule; destructors should never throw exceptions.
Oh, there is one case in which shared_ptr cannot help you if an exception is thrown, and that is this case:
Code:
myFunction(MyObjectPtr(new MyObject),myFunction2());
In this case, we know that "new MyObject" must be evaluated before MyObjectPtr's constructor, but compilers can do the call to myFunction2() anytime. So if your compiler executed in this order:
1) new MyObject
2) myFunction2()
3) MyObjectPtr::MyObjectPtr()
then you could leak resources if myFunction2() throws an exception. You should NEVER use this code like this, even if you can assure me that your compiler doesn't use that particular order of execution. The compiler is ALLOWED to do things in that order. Yes, there are 3 different orders that it could execute in, and that is the least likely for a compiler to use, but your code should work under ANY legal compiler, and the above order is legal if unusual.
The safe way to do the above call is:
Code:
MyObjectPtr p(new MyObject);
myFunction(p,myFunction2());
Now the object is destroyed even if myFunction2() throws.
By the way, the following code is safe, because the compiler has no freedom in order of executions: It must first call new, then MyObjectPtr's constructor, then vector::push_back(). There is no place for an exception to occur between the call to new and the call to the smart pointer's construction.
Code:
mov.push_back(MyObjectPtr(new MyObject));
Do be warned now that shared_ptr isn't a magic bullet that stops all possible leaks. There are ways you can force a memory leak, even with shared_ptr; the main one is an object with a shared_ptr to itself (e.g. say you have a shared_ptr named A that points to an object B, which has a shared_ptr that is a copy of A). In that case, you'll always leak (B can't be destroyed until A's reference count is zero, but A's reference count can't be zero until B's destructor releases the pointer). Boost also provides a means of dealing with this (weak_ptr), for situations where B really needs to have a shared_ptr to itself.
So, the bottom line of my advice is use shared_ptr (or a similar template class from another library) whenever it makes sense, but be careful, because it's still possible to make yourself leak memory unintentionally. Remember it's ALWAYS possible to leak, and nothing you do can prevent it, but you can minimize how the leaks can happen.