Sign in to follow this  
chiranjivi

Help a rank beginner understand inheritance

Recommended Posts

I'm trying to get my head around inheritance at the moment and thought I would write a simple game containing a base class Enemy and virtual function move(), and various Enemy subclasses (eg. Dragon, Skeleton or whatever) with their own specific move() methods. I then instantiated some Dragons and Skeletons, put them into an Enemy vector, and tried to call their move() functions, but it didn't work. In an effort to find out why, I've been hacking through some very simple examples, like this one. Here's the classes, lifted straight from the example:
class BaseClass {
public:
  int i;
  BaseClass(int x) {
     i = x;
  }
  virtual void myFunction()
  {
    cout << "Using BaseClass version of myFunction(): ";
    cout << i << '\n';
  }
};

class DerivedClass1 : public BaseClass {
public:
  DerivedClass1(int x) : BaseClass(x) {}
  void myFunction()
  {
    cout << "Using DerivedClass1's version of myFunction(): ";
    cout << i*i << '\n';
  }
};

class DerivedClass2 : public BaseClass {
public:
  DerivedClass2(int x) : BaseClass(x) {}
  void myFunction()
  {
    cout << "Using DerivedClass2's version of myFunction(): ";
    cout << i+i << '\n';
  }
};

And here's a modified int main().
int main()
{
    cout << endl << "Using a vector to iterate through the objects..." << endl << endl;

    vector<BaseClass> bcvector;

    BaseClass b1(10); bcvector.push_back(b1);
    DerivedClass1 d1(10); bcvector.push_back(d1);
    DerivedClass2 d2(10); bcvector.push_back(d2);

    for(vector<BaseClass>::iterator i = bcvector.begin(); i != bcvector.end(); ++i)
    {
        i->myFunction();
    }

    cout << endl << "Using a pointer to the objects..." << endl << endl;

    BaseClass *p;
    BaseClass ob(10);
    DerivedClass1 derivedObject1(10);
    DerivedClass2 derivedObject2(10);

    p = &ob;
    p->myFunction();

    p = &derivedObject1;
    p->myFunction();

    p = &derivedObject2;
    p->myFunction();

    return 0;
}

This returns the following:
Quote:
Using a vector to iterate through the objects... Using BaseClass version of myFunction(): 10 Using BaseClass version of myFunction(): 10 Using BaseClass version of myFunction(): 10 Using a pointer to the objects... Using BaseClass version of myFunction(): 10 Using DerivedClass1's version of myFunction(): 100 Using DerivedClass2's version of myFunction(): 20 Process returned 0 (0x0) execution time : 0.046 s Press any key to continue.
I don't understand why when I call the methods via the vector, the methods of the base class are always called, but when done via a pointer, the intended subclass methods are used instead. Is there a way I can hold objects in a container like this and have the appropriate methods be called?

Share this post


Link to post
Share on other sites
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.

When you virtualize a function, you can choose to completely replace that function with another function or extend it by calling the parent function from within the derived class function.

here's an example.

class DerivedClass2 : public BaseClass {
public:
DerivedClass2(int x) : BaseClass(x) {}
void myFunction()
{
BaseClass::myFunction();
cout << "Using DerivedClass2's version of myFunction(): ";
cout << i+i << '\n';
}
};

What you could also do is make your base class function a pure virtual.

class BaseClass {
public:
int i;
BaseClass(int x) {
i = x;
}
virtual void myFunction() = 0;
};

class DerivedClass1 : public BaseClass {
public:
DerivedClass1(int x) : BaseClass(x) {}
void myFunction()
{
cout << "Using DerivedClass1's version of myFunction(): ";
cout << i*i << '\n';
}
};

Share this post


Link to post
Share on other sites
Hi howie, thanks for your reply.

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.


Ha, I suppose it shows how little I know about it that I don't even understand the terminology :/

Quote:
Original post by howie_007
What you could also do is make your base class function a pure virtual.

*** Source Snippet Removed ***


If I do this, and then try the vector vs. pointer code in int main() again, the compiler gets angry at this point:


DerivedClass1 d1(10);
bcvector.push_back(d1);



...and refuses to allow me to add a derived object to a vector<BaseClass>. It says:

Quote:

error: cannot allocate an object of type `BaseClass'
error: because the following virtual functions are abstract:
error: virtual void BaseClass::myFunction()


How am I going about things wrong here?

Share this post


Link to post
Share on other sites
Quote:
Original post by chiranjivi
Is there a way I can hold objects in a container like this and have the appropriate methods be called?

Hold a pointers to objects in a container, for example:
Quote:

std::vector<Object*> container;

Share this post


Link to post
Share on other sites
You cannot put objects of derived classes into a vector of base class. Polymorphism requires a level of indirection. Try a vector<BaseClass*>.

Share this post


Link to post
Share on other sites
stalk3r, DevFred,

Thanks for your help, I'm getting there slowly but surely (knowing that what I'm trying to accomplish is polymorphism and not inheritance has made my Googling rather more productive as well).

Share this post


Link to post
Share on other sites
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.

Share this post


Link to post
Share on other sites
Quote:
Original post by Zahlman
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.

In other words, a template method? :-)

Share this post


Link to post
Share on other sites
OK, so I've got this far:


vector<Enemy*> enemies;

for ( int i = 0; i != 5; ++i ) { enemies.push_back( new Dragon() ); }
for ( int i = 0; i != 5; ++i ) { enemies.push_back( new Skeleton() ); }

for ( vector<Enemy*>::iterator i = enemies.begin(); i != enemies.end(); ++i )
{
(*i)->act();

if ( (*i)->hitpoints<=0 )
{
(*i)->~Enemy();
enemies.erase(i); --i;
}
}



This code compiles, and works as far as the polymorphism goes (ie. each derived class will utilise its own act() method), but the game crashes at random, presumably because I'm not paying any attention to dynamic allocation as mentioned earlier.

From the reading I have done I understand I have to use smart pointers or similar, but the resources I have been able to find tend to be a little much for the titular rank beginner. I understand libraries exist that can obviate the need for handling this myself, but I worry that I won't learn if I take this route at the first opportunity.

I would appreciate it if someone could gently push me in the right direction.

Share this post


Link to post
Share on other sites
This:
(*i)->~Enemy();

Should be:
delete *i;

With the former, the most derived destructor (if Enemy::~Enemy is virtual) is called, but the memory allocated for that Enemy is not returned to the system. With the latter, the most derived destructor is called, and the memory is returned to the system. However the phrase, "the game crashes at random," could indicate problems elsewhere.

Share this post


Link to post
Share on other sites
Also, your iterator probably gets invalidated. Use the return value of erase.
for ( vector<Enemy*>::iterator i = enemies.begin(); i != enemies.end(); )
{
(*i)->act();

if ( (*i)->hitpoints<=0 )
{
delete (*i);
i = enemies.erase(i);
}
else
{
++i;
}
}

Share this post


Link to post
Share on other sites
Quote:
Original post by _fastcall
This:
(*i)->~Enemy();

Should be:
delete *i;

With the former, the most derived destructor (if Enemy::~Enemy is virtual) is called, but the memory allocated for that Enemy is not returned to the system. With the latter, the most derived destructor is called, and the memory is returned to the system.


I'll bear that in mind, thanks.

Quote:
Original post by _fastcall
However the phrase, "the game crashes at random," could indicate problems elsewhere.


It certainly could, yeah. If I implement separate vector<Dragon> and vector<Skeleton> containers and compile the game that way, though, it doesn't crash, so I assumed the problem must lie with my sophomoric attempt at polymorphism here. Are you saying that I should be able to implement a vector of pointers to derived objects in this fashion and it should be entirely stable?

Share this post


Link to post
Share on other sites
Here's one to help that destructor

(One thing I hate about C and C++ is the obsession with using as few words as possible in different places to mean different things)

Make the base class desctructor declaration virtual

ie


public:
foo();
virtual ~foo();





This allows you to then call the destructor on a base object and it knows to call the derived classes destructor too, else it won't. If you have "bar" inherited from "foo", deleting "foo" will now also call the destructor for "bar" automatically. So you only ever need to delete the base class pointers when you have them in lists etc.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this