🎉 Celebrating 25 Years of GameDev.net! 🎉

Not many can claim 25 years on the Internet! Join us in celebrating this milestone. Learn more about our history, and thank you for being a part of our community!

C++ Workshop - Inheritance (Ch. 12)

Started by
39 comments, last by Dbproguy 16 years, 1 month ago
Hey, I only wrote it in C so you'd have a good example of how not to do it. :)
Advertisement
[OPINION, but based on actually implementing this kind of thing several times for the benefit of random FB forum threads]
Creating an object to represent a menu is likely to be useful. Involving inheritance is not.

Normally, inheritance is invoked to provide differences in behaviour: the general interface of the base class says what classes in the hierarchy can do, and the subclasses implement different *ways of doing that*. But you should first check to see if you can make it work by using the same class and varying the *data that is worked upon*. For example, think of the items of a menu, and the title, as components of the menu. Can you see how you don't need any inheritance to represent different menus? You just put different items into each menu instance. Now apply the same analysis to those components: what can a menu item "do"? How many sensible ways are there to do it?
[/OPINION]
[opinion]
I can see how to do this in many different ways.

I could do it via procedural methods, like you would do in C, I can see doing it simply with OO and I can see using inheritence. I think I will do the project in two ways, both simple OO and making good use of inheritence.
[/opion]

Also, regardless whether this specific project makes good use of inheritence or not, you should still learn to make good use of it because it is an invaluable tool.
what's the difference between what you call "simple OO" vs. inheritance?
[opinion]
Simple OO would be to simply treat everything as an object.

So, for example, you could have a class Car.

class Car{private: // member data ...public: //constructors ... //member functons void startEngine(); //turn on the cars engine void accelerate(); //accelerate the car's speed void decelerate(); //slow the car down. void stopEngine(); //turn off the cars engine};


and the Car class would have every member function it needs inside of it.

Now, if you were going to create a variety of wheeled vehicles, you could do this more effectively by created a base class to derive them from and then creating child that have the specifics. So, we could have a WheeledVehicle class and then have a Car class derivated.

class WheeledVehicle{private: // member data ...public: //constructors ... //member functons void accelerate(); //increase the vehicle's speed void decelerate(); //decrease the vehicle's speed};class Car : public WheeledVehicle{private: // member data ...public: //constructors ... //member functons void startEngine(); //turn on the car's engine void stopEngine(); //turn off the car's engine //No need to redefine accelerate() or decelerate() because they are inherited  // from WheeledVehicle.};


Using inheritence, you could create many different types without having to recreate anything. From WheeledVehicle you could also derive Bicycle which would have pedal() and from car you could have classes like SportsCar, SUV, etc.

Basically, you could do it either way, but when you use inheritence, you reuse code the you have already written, which is one of the main purposes of OO, and you can create new classes that share similarities with each other.

There is no need to reinvent the wheel.
[/opinion]

And, I haven't noticed it anywhere yet, so:
[warning]
Be sure that when you declare a child, you have the parent as public.

Example:

class Car : WheeledVehicle
{
//definitions
...
};


will give you an error when you try to call protected member functions or data of WheeledVehicle.

class Car : public WheeledVehicle{ //definitions ...};


will let you use WheeledVehicle's protected member functions or data without problem.
[/warning]

[Edited by - samanime on August 24, 2006 3:52:36 PM]
[Opinion - of Scott Myers, Author of the Effective C++ series, and of Herb Sutter, Author of the Exceptional C++ series - and also of me.]
When using inheritence, you must understand what the different kinds of inheritence mean.

Public inheritence models an 'is-a' relationship. For example, if Tabby inherits publicly from Cat, then it means that 'Tabby is a Cat'. Not that Tabby is a kind of Cat; and this is the confusing part - knowing the difference between 'is a' and 'is a kind of'. When a function takes a Cat*, that pointer can point to a Cat or to anything derived from Cat. Therefore, if Cat makes certain guarantees about it's state, derived classes must uphold them. From the view of the code using the Cat*, it must not matter in any way wether the pointer points to a Cat or to a Tabby. This is called the Liskov Substitution Principle.
The best example of a violation of this principle that I've yet found is this.
Imagine you have a class 'Rectangle' which has the virtual member funtions 'set_width' and 'set_height'. You derive 'Square' publicly from 'Rectangle' because, of course, a Square 'is a' Rectangle. Unfortunatly, while this may be true in mathmatics, it fails when subjected to the substitution test. When using Rectangles, if you 'set_width', the height remains unchanged, and vice versa. This isn't true for squares. Squares must maintain the additional invariant that the width and height must be the same. Calling set_width on a square must also change the height. Therefore, the class does not satisfy the Liskov Substitution Principle. We come to the conclusion that derived classes can only apply additional invariants when they do not break the invariants imposed by the parent class.

Protected inheritence models an 'is implemented in terms of' relationship, and is the preffered method of inheritence when you are interested in code reuse and there is no other method available, or if you need access to protected members of the class. Remember that only public inheritence allows you to use a derived type as if it were the parent type.

Private inheritence models a 'has a' relationship, and is little different them simple containment. An example -
class Foo {};//private inheritenceclass Bar1 : private Foo {}; //Bar1 'has a' Foo//containmentclass Bar2  //Bar2 also 'has a' Foo{   Foo m_foo; };

Private inheritence differs greatly from Containment in syntax. It unfortunatly has few advantages over Containment. The one most often touted is the 'empty base' optimization. Even if a class is empty, (That is; has no data members and no virtual functions) the compiler must give it atleast one byte of storage. This is because every variable must have a unique address, and if a class took up zero space the next variable declared would have the same address (I am simplifying. All sorts of things could end up at that same address) In some cases, when a class derives from an empty class, the compiler does NOT need to give any space inside the derived class (Bar1 above) to the parent class (Foo above). This is not possible with containment, and it is often not possible with public inheritence either (Depending greatly on how the compiler implements stuff)
[/Opinion]
I've found this chapter fascinating - but have come across something that I don't understand. I realise the use of virtual methods but don't know why destructors (i.e. Listing 12.8, lines 9 & 21 and Listing 12.9, line 9) are also designated as virtual. I removed both virtuals in Listing 12.8 and the console didn't report the dog destructor as having been called.

There must be some logic in having destructors as virtual because I've seen them designated as such in other listings, I just don't know why!
Except for being automatically called at the end of a variable's lifetime, a destructor is just like any other member function. Thus, it needs to be virtual if it will be used polymorphically, i.e. if you will create a pointer to base, point it to a derived class, and then try to deallocate the derived object via the base pointer. If the destructor were not virtual, then the 'delete' call would be forced to use the *static* type of the pointer, i.e. Base*, and invoke that destructor.

See more here.
c'mon, what happened to the noise, everyone still on vacation or something?
and when is week 11 chapter 13 called for?
There is reference in a couple of places that "nested" derived classes are possible (Mammal, Pet, Dog etc.) but is there a restriction on the number of such nested classes imposed by the language or compiler? I suspect that the answer is no and, if that's the case, any restriction is self-imposed by the programmer in the interest of being able to read and understand the code.

If my assumption is correct, what would folks say is a reasonable maximum number of nested derived classes, for instance when creating a really large and complicated application?

This topic is closed to new replies.

Advertisement