OP: For an explanation of your problem, skip to the first horizontal bar. There is still a lot to read, but it really is important to understand all of this. You can't do these things properly in C++ without a full understanding.
Quote:Original post by howie_007
What you have going here is polymorphism and not inheritance. Inheritance is your derived class can use the functions and data members in your base class. So in your case you're completely replacing it.
You're making a mess of the terminology, sorry.
"Polymorphism" is the general concept of being able to use more than one thing in the same way, and have the used thing do different, but appropriate, things as a result. You don't even need classes to get polymorphic behaviour.
"Inheritance" is the concept of being able to define a class which includes the data and functionality of a base class, which can then be replaced or extended. The OP does this. It's not required for OO.
There's another important concept: "subtyping" is the concept of being able to define a type that describes some of the things that are described by a broader type. For example, "Car" is a subtype of "Vehicle"; some Vehicles are Cars.
In languages - such as C++ - where classes are normally used to define types, usually inheritance is possible, and is the normal way to implement subtyping. Properly designed subtypes are polymorphic; if a function expects a Vehicle, you should be able to give it a Car and have it work properly.
However, there is a complication in C++: because of the combination of
static typing and
default value semantics for objects, the language makes a distinction between the compile-time type and the run-time type of a variable.
Thus: in C++, when you have a pointer or reference to a base type, it may point at an instance of the derived type; but when you have a variable that holds an instance of the base type, or an array of instances of base types (or a standard library container, because they hold their elements in those ways)
you may only store an instance of the base type. That is because, conceptually, there is only room in the variable for the base type instance; instances of the derived types won't fit. (The derived instance is at least as big; it could be exactly the same size, if you're careful, but it still isn't allowed.)
Whenever you assign from a derived instance to a base instance, or create a base instance using a derived instance (for example, with the copy constructor, or implicitly by putting the instance into a std::vector), the base instance is still a base instance. It cannot change its type. Thus, only the part of the derived instance that's common with base instances - the "base part of the class" - gets used. This is called
object slicing, and is one of the most annoying things about trying to do OOP in C++. To get the polymorphic behaviour you need to look after both parts: tell the compiler to call the function polymorphically (by using 'virtual'), and be careful about storage.
Once you get around that, you will find yourself running into another obstacle: since a normal variable or an array of the base type can't hold the derived instance, you usually end up using dynamic allocation in one form or another. (You can still, for example, take a derived instance and pass it to a function that expects a reference to a Base, but that's not very useful. You can't even take an array of derived instances and pass it to a function expecting a pointer-to-Base, because it won't know the right size for stepping over the array. This is a problem with how C++ treats arrays in general.)
Dynamic allocation is tricky. First of all you have to take care of the cleanup for memory. You can use smart-pointers to help with that (or replace your containers with the boost::ptr_containers), but you'll also be annoyed when you have to copy or assign the instances: the copy constructor and assignment operator cannot be made 'virtual'. Finally, when you actually do the cleanup, you have to keep in mind that calling 'delete' on a pointer calls the destructor, and looks up which destructor to use following the normal rules. Fortunately, the destructor
can be made virtual, and you
should do so whenever you have any other virtual function, as a rule; it costs basically nothing and is probably required for correctness.
Quote:or extend it by calling the parent function from within the derived class function.
Ugh. This is often
a bad idea. Instead, have the parent function run some non-virtual code for the common work, and then call a pure virtual function where each derived class implements the rest of the work.