540 likes | 853 Views
Inheritance Version 1.1. Topics. Inheritance Constructors and Inheritance Function Over-riding (Redefinition) The Stringstream class Shadowing Variables Destructors and Inheritance Pointers and Inheritance Upcasting and Downcasting Multiple Inheritance. Objectives.
E N D
Topics Inheritance Constructors and Inheritance Function Over-riding (Redefinition) The Stringstream class Shadowing Variables Destructors and Inheritance Pointers and Inheritance Upcasting and Downcasting Multiple Inheritance
Objectives After completing this topic, students should be able to: Correctly design and use classes that use public inheritance in a C++ program * Know how to correctly call the parent constructor * know how to over-ride functions in the parent class Write functions that generate a string representation of a class, using the stringstream class Describe the differences between upcasting and downcasting and know when to safely use each Correctly use multiple inheritance in a program
Inheritance is the act of deriving a new class from an already existing class. Analogous to creating a new house blueprint from an existing one. Inheritance is useful when we find a natural hierarchical relationship between classes. Inheritance makes it possible to re-use existing code (classes), thus saving time and minimizing bugs.
Suppose that we are creating a new role playing game, and we want to have the following creatures: Dwarf Elf Fairy
All of these are “creatures “. They all have • A name • A strength value • A hitpoint value Dwarf Elf Fairy
We could define a class for each such as: • class Dwarf • { • private: • string name; • int strength; • inthitpoints; • //other dwarf stuff • public: • Dwarf( ); • getDamage( ); • //other dwarf stuff • }; • class Elf • { • private: • string name; • int strength; • inthitpoints; • //other elf stuff • public: • Elf( ); • getDamage( ); • //other elf stuff • }; • class Fairy • { • private: • string name; • int strength; • inthitpoints; • // other fairy stuff • public: • Fairy( ); • getDamage( ); • //other fairy stuff • };
Note that all of these classes have some things in common • class Dwarf • { • private: • string name; • int strength; • inthitpoints; • … other … • public: • Dwarf( ); • getDamage( ); • . . . • }; • class Elf • { • private: • string name; • int strength; • int hitpoints; • … other … • public: • Elf( ); • getDamage( ); • . . . • }; • class Fairy • { • private: • string name; • int strength; • int hitpoints; • … other … • public: • Fairy( ); • getDamage( ); • . . . • };
And they have some things that are different • class Dwarf • { • private: • string name; • int strength; • inthitpoints; • // dwarf stuff • public: • Dwarf( ); • getDamage( ); • // dwarf stuff • }; • class Elf • { • private: • string name; • int strength; • inthitpoints; • // elf stuff • public: • Elf( ); • getDamage( ); • // elf stuff • }; • class Fairy • { • private: • string name; • int strength; • inthitpoints; • //fairy stuff • public: • Fairy( ); • getDamage( ); • // fairy stuff • };
We gain a lot of productivity and functionality if we factor the common elements out into a base class. Creature name strength hitpoints Base class • Sometimes also called • parent class • super class This relationship is called Inheritance. Derived Class • Sometimes also called • child class • sub class Fairy Dwarf Elf
If elves and fairies are both magical creatures, we might envision another layer of base and derived class Creature Base class name strength hitpoints Magical Creature Derived Class spells Dwarf Derived Class Fairy Elf
This is known as a class hierarchy Creature name strength hitpoints Dwarf inherits from Creature Magical Creature inherits from Creature Magical Creature spells Dwarf Elf and Fairy inherit from Magical Creature Elf Fairy
This is known as a class hierarchy Creature name strength hitpoints Common things are defined in base classes Magical Creature spells Dwarf Unique things are defined in derived classes Elf Fairy
Let’s look at the base class definition Creature name strength hitpoints Class Creature { protected: string name; int strength; int hitpoints; public: Creature( ); int getDamage( ); . . . }; the protectedmodifier (#) tells us that the variable name is accessible from within the Creature class and from within any derived classes. That is, functions of the derived class can see the protected data members defined in the base class.
And the MagicalCreature class Creature name strength hitpoints Class MagicalCreature : public Creature { protected: int spells; public: MagicalCreature( ); . . . }; Magical Creature spells The keyword public means that this class will inherit publicly from the Creature class. That is, a MagicalCreature object will have everything that a Creature object has, and everything declared as public in the base class will be public in the derived class.
So . . . If I create a MagicalCreature • object named Zor, then Zor has the • following properties: • name • strength • hitpoints • spells - This comes from the MagicalCreature class These come from the Creature class
This kind of inheritance is known as an is-a(is-like-a) relationship. That is, a MagicalCreatureis a Creature. An object of the MagicalCreature class can be used anyplace that an object of the Creature class can be used.
Let’s look at how we deal with differences between classes by Looking at the getDamage( ) function.. getDamage( ) computes and returns the damage that this creature inflicts in one round of combat. Every creature inflicts damage that is a random number between 1 and the creature’s strength value. Dwarf Fairy Elf
Magical creatures can double the damage points if they have a magic spell. Dwarves are good fighters, and have a 25% chance of inflicting an additional 50 damage points Fairies are very quick, so they get to attack twice.
Since the damage that any creature can inflict is defined as a random number between 0 and the creature’s strength value, we could write the following code in the base class: Creature name strength hitpoints int Creature::getDamage( ) { int damage = (rand( ) % strength) + 1; return damage; }
Dwarves have a 25% chance of inflicting an additional 50 damage points. So the getDamage( ) function in the Dwarf class might look like: Creature name strength hitpoints int Dwarf::getDamage( ) { int damage = (rand( ) % strength) + 1; if (rand( ) % 100 < 25) damage = damage + 50; return damage; } Dwarf
Notice that both the Creature class and the Dwarf class have a function named getDamage( ); This is called Function Over-riding. int Creature::getDamage( ) { int damage = (rand( ) % strength) + 1; return damage; } Creature name strength hitpoints int Dwarf::getDamage( ) { int damage = (rand( ) % strength) + 1; if (rand( ) % 100 < 25) damage = damage + 50; return damage; } Dwarf
Function Over-riding We over-ride, a member function in the base class by writing a similar function in the derived class that has exactly the same signature, but with a different implementation. This is also called function re-definition.
So, if I create a Dwarf object named dwarf01, and send dwarf01 a getDamage( ) message . . . dwarf01.getDamage( ) dwarf01 The getDamage( ) function in the Dwarf class over-rides the getDamage( ) function in the base class, and so the getDamage( ) function in the Dwarf class gets executed.
Note that if we had not re-defined the getDamage( ) function in the Dwarf class, then it would have been the getDamage( ) function from the base class that would have been executed. This is because a Dwarf is-a(is-like-a) creature.
Let’s let dwarves have a unique property of size. The class definition then might be: dwarf Class Dwarf : public Creature { private: int size; public: Dwarf( ); Dwarf(string, int, int, int); int getDamage( ); . . . };
Creature Part Now create a Dwarf object … Dwarf dwarf01 ( “Rohan”, 350, 500, 25); When the constructor is called, the computer looks at the Dwarf class to see how much storage to allocate. It notes that a Dwarf is a Creature, so it looks at the Creature class also. Enough storage is allocated for the data members of both the base and the derived classes. name strength hitPoints Dwarf Part size dwarf01
The Dwarf Constructor Dwarf::Dwarf (string _nme, int _strngth, int _hpts, int _sze) : Creature (_nme, _strngth, _hpts) { size = _sze; } Constructors are not inherited. In order to initialize the parent class we must invoke the base class constructor. This is done with an initializer list. If you do not explicitly call the Creature constructor, the default Creature constructor is called automatically
Shadowing Variables If a child class declares a variable using the same name as a variable in a parent class, the variable in the child class is said to shadow the variable in the parent. Shadowing variables is not usually recommended.
The stringstream classes support reading from or writing to a buffer in memory, just as if we were reading from or writing to a stream. #include <iostream> #include <sstream> using namespace std; int main( ) { double number = 3.21467; ostringstream s, t; s << "Hello World!"; t << "\nGoodbye Cruel World!"; t << “\nnumber = “ << number; cout << s.str( ) + t.str( ); system(“PAUSE”); return 0; } ostringstream acts just like an output stream class.
The stringstream classes support reading from or writing to a buffer in memory, just as if we were reading from or writing to a stream. #include <iostream> #include <sstream> using namespace std; int main( ) { double number = 3.21467; ostringstream s, t; s << "Hello World!"; t << "\nGoodbye Cruel World!"; t << “\nnumber = “ << number; cout << s.str( ) + t.str( ); system(“PAUSE”); return 0;} Use stream insertion just like with any other output stream object
The stringstream classes support reading from or writing to a buffer in memory, just as if we were reading from or writing to a stream. #include <iostream> #include <sstream> using namespace std; int main( ) { double number = 3.21467; ostringstream s, t; s << "Hello World!"; t << "\nGoodbye Cruel World!"; cout << s.str( ) + t.str( ); system(“PAUSE”); return 0; } ostringstream has a member function str( ) that returns the contents of the stream as a string.
Lets make it possible for each creature to generate an output string describing itself. My name is Rohan. My strength is 300 and my hitpoint value is 200. I am a dwarf and my size is 3. My name is Elgier. My strength is 235 and my hitpoint value is 90. I am a magical creature and I have 3 spells. I am an Elf. My name is Moondrops My strength is 190 and my hitpoint value is 75. I am a magical creature and I have 3 spells. I am a fairy and my speed is 16.
The toString( ) function in the Base class: string Creature::toString( ) { ostringstream creatureOut; creatureOut << “My name is “ << name << “.\n” << “My strength is “ << strength << “,\n” << “my hitpoint value is “ << hitPoints << “.”; return creatureOut.str( ); }
The toString( ) function in the MagicalCreature class: string MagicalCreature::toString( ) { ostringstream magicalOut; magicalOut << “I am a Magical Creature, and I have ” << spells << “ spells.”; return this->Creature::toString( ) + magicalOut.str( ); }
The toString( ) function in the Fairy class: string Fairy::toString( ) { ostringstream fairyOut; fairyOut << “I am a fairy, and my speed is “ << speed << “.”; return this->MagicalCreature::toString( ) + fairyOut.str( ); }
cout << bestFairy.toString( ) invokes string Fairy::toString( ) { ostringstream fairyOut; fairyOut << “I am a fairy, and my speed is “ << speed << “.”; return this->MagicalCreature::toString( ) + fairyOut.str( ); } invokes string MagicalCreature::toString( ) { ostringstream magicalOut; magicalOut << “I am a Magical Creature, and I have ” << spells << “ spells.”; return this->Creature::toString( ) + magicalOut.str( ); } invokes string Creature::toString( ) { ostringstream creatureOut; creatureOut << “My name is “ << name << “.\n” << “My strength is “ << strength << “,\n” << “my hitpoint value is “ << hitPoints << “.”; return creatureOut.str( ); }
Destructors Destructors are not inherited. However, when a destructor in a derived class is invoked, it automatically invokes the destructor for the base class.
Order of Construction and Destruction When an object of a derived class is created, the derived class constructor is called first, it then calls the base class constructor. The base class constructor completes and then the derived class constructor completes. Destruction occurs in the opposite order. The derived class destructor is called (destroys the derived object), it completes then it calls the base class destructor which destroys the base object and completes.
Protected Inheritance A derived class can be defined using protected inheritance, as class Circle : protected Shape { … Protected inheritance means that public data in the base class becomes protected in the derived class when it is inherited.
Private Inheritance A derived class can be defined using private inheritance, as class Circle : private Shape { … Private inheritance means that public data in the base class becomes private in the derived class when it is inherited.
Visibility In the case of public inheritance Member functions in a derived class can access Public data and functions in the base class Protected data and functions in the base class Functions outside of the derived class can access public data and functions in the base class public data and functions in the derived class
Inheritance and Pointers Because of the is-a (is-like-a) relationship, an object of a publicly derived class can always be treated as an object of the corresponding base class. In particular, you can always store the address of a derived class object in a base class pointer.
Dwarf littleDwarf (“Egrew”, 600, 500, 2); Creature* littleCreaturePtr; littleCreaturePtr = &littleDwarf; littleDwarf is A Dwarf object. littleCreaturePtr littleCreaturePtr is a Creature pointer. littleDwarf
If you now use the Creature pointer, it treats littleDwarf as if it were an creature. For example … cout << littleCreaturePtr -> toString( ); invokes string Creature::toString( ) { ostringstream creatureOut; creatureOut << “My name is “ << name << “.\n” << “My strength is “ << strength << “,\n” << “my hitpoint value is “ << hitPoints << “.”; return creatureOut.str( ); } littleCreaturelPtr littleDwarf
This works because the derived class object is really made up of two parts, the base class part and the derived class part. The base class pointer just points to the base class part. base part derived part littleCreaturePtr littleDwarf
You can store the address of a base class object in a derived class pointer, but you must do an explicit downcast first! Dwarf *littleDwarf; Creature anyOne (“joe”, 400, 190); littleDwarf = (Dwarf*) (&anyOne);
This is pretty dangerous and not often used, because the derived class pointer thinks it is pointing to a derived class object, but it really isn’t. This is referred to as the “slicing problem”. base part littleDwarf there is no derived part. If you try to access member data in the derived part, you will get garbage! joe