Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double...
-
Upload
martina-brooks -
Category
Documents
-
view
218 -
download
3
Transcript of Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double...
Inheritance
Recall the plant that we defined earlier…
class Plant{
public: Plant( double theHeight ) :
hasLeaves( true ), height (theHeight) { }Plant( bool withLeaves=true, double theHeight = 1) :
hasLeaves( withLeaves ), height (theHeight) { }void Grow(double growBy = 1) { height += growBy ; }
private:bool hasLeaves;double height;
};
Suppose we need to define special plant – a flower
A flower plant can grow and may or may not have leaves, just like a regular plant
But it has additional properties – number of flowers and whether they are open or not
We can define the new class FlowerPlant by copying and modifying the definition of Plant. Very bad practice !!!
Is there a better way?
FlowerPlant is a Plant
We can define FlowerPlant as inheriting from Plant
We say that FlowerPlant is a (kind of) Plant
This means that FlowerPlant can do everything a Plant can do, and possibly more
Note the difference between “is a” relationship (FlowerPlant-Plant) and “has a” relationship (FlowerClock-Plant)
FlowerPlant defined
class FlowerPlant: public Plant{ public: FlowerPlant( bool flowersOpen = true, size_t numFlowers = 1, double theHeight = 1) : Plant(true, theHeight), _flowersOpen( flowersOpen ), _numFlowers (numFlowers) { } size_t GetNumFlowers() const { return _numFlowers; } void OpenFlowers() { _flowersOpen = true; } void CloseFlowers() { _flowersOpen = false; } bool IsFlowering() const { return _flowersOpen; }
private: bool _flowersOpen; size_t _numFlowers;};
All right, what’s going on here?
Now we can do:FlowerPlant roseBush(false, 5, 10);roseBush.Grow(10);roseBush.OpenFlowers();
Constructing FlowerPlant
Each FlowerPlant object (such as roseBush in the above example) has a “Plant” part inside it:
When we called Grow in the example, we talked to the “Plant” part of FlowerPlant
roseBush
Plantfields
Plantinterface
FlowerPlantfields
FlowerPlantinterface
When a FlowerPlant is created, the “Plant” part must be initialized first (before the FlowerPlant part)
How? A constructor of Plant must be called in the initialization line
If we don’t do it – the compiler tries to call the default constructor of Plant
Good practice: always write this call explicitly
Destructing FlowerPlant
What happens when roseBush goes out of scope?
The destructor of FlowerPlant is calledSince we didn’t define one, the compiler
created it automatically – as:
~FlowerPlant() {} Does it do anything? Yes, it does!
Destructing FlowerPlant
A destructor always does the following:1. Executes the code in its scope, i.e. in { } 2. Automatically calls the destructors of the fields
defined in this class3. Automatically calls the destructor of the base
class Two common mistakes with destructors:
Never call a destructor explicitly!Never handle base class fields in the inheriting
class destructor – let the base class destructor take care of that
Assigning/copying plants Is this legal? And what will it do?
Plant otherPlant(false);FlowerPlant roseBush(false, 5, 10);
1) roseBush = otherPlant; 2) otherPlant = roseBush;
Recall that the compiler automatically created operator= and copy constructor for Plant and FlowerPlant
The first line does not compile – there is no assignment operator for FlowerPlant from Plant
The second line works, and copies only the Plant part of roseBush
“slicing” – cutting parts off the object to fit the copy target, when passed by value as a base class object
Assigning/copying plants cont.
Is this legal? And what will it do?void TakeGoodCare(Plant p) {…}FlowerPlant roseBush(false, 5, 10);TakeGoodCare(roseBush);
When TakeGoodCare is called, a local copy of roseBush is created
The copy is of type Plant – again, slicing occurs! (this time, using a copy constructor of Plant)
We probably intended the function to bevoid TakeGoodCare(Plant& p) {…}
And then there is no copy and no slicing – function operates on the given object directly
Assigning/copying plants cont.
Is this legal? And what will it do?Plant myPlants[5];
FlowerPlant roseBush(false, 5, 10);myPlants[0] = roseBush;
Slicing again – by definition, an array of Plants holds only Plants
What if we need a mixed array of simple Plants and FlowerPlants?
Mixing Plants with Flowers
We have to use an array of pointers to Plant and dynamic allocation:
Plant* myPlants[5];myPlants[0] = new FlowerPlant(roseBush);myPlants[1] = new Plant();
And then the following code works as expected (assuming all elements are initialized as above):
for (size_t i=0; i<5; i++){ myPlants[i]->Grow(3.14);}
Upgrading FlowerPlant
We want to add functionality to the Grow function of FlowerPlant
We want it to increase the number of flowers as well as the height
We define (in FlowerPlant):
void Grow(double growBy = 1) { height += growBy ; _numFlowers += size_t(growBy) ;}
And it doesn’t compile height is private to Plant - what to do?
Upgrading FlowerPlant There are two options One is to call the public function Grow of Plant:
void Grow(double growBy = 1) { Plant::Grow(growBy) ; _numFlowers += size_t(growBy) ;}
By defining a new Grow in FlowerPlant we hide the Grow in Plant
Note that we need to specify the base class in the call – otherwise it would be an infinite recursion!
Upgrading FlowerPlant
The other is to keep the Grow function as:
void Grow(double growBy = 1){ height += growBy ; _numFlowers += size_t(growBy) ;}
And define height as protected instead of private
protected members are accessible only to this class and inheriting classes
Which solution is better?
Upgrading FlowerPlant
Now the following works as expected:FlowerPlant roseBush(false, 5, 10);roseBush.Grow(10);
What about:Plant* myPlants[5];myPlants[0] = new FlowerPlant(roseBush);myPlants[1] = new Plant();myPlants[0]->Grow();myPlants[1]->Grow();
In both lines, Grow of Plant is called We need a way to select the function to call according
to the actual type of the object, and not the compile-time type of the pointer!
Upgrading FlowerPlant properly
If we want the code to call the correct implementation of Grow, according to the run-time type of the object: Plant::Grow if it is a Plant and FlowerPlant::Grow if it is a FlowerPlant,
Grow need to be defined as virtual in Plant:
class Plant{
public: …
virtual void Grow(double growBy = 1) { height += growBy ; } …};
Upgrading FlowerPlant properly
If a function is declared as virtual in the base class (Plant), it will automatically be virtual in the derived classes (FlowerPlant) as well – no changes necessary
Yet, it is a good practice to write “virtual” when overriding as well – the code is more readable:
class FlowerPlant: public Plant{public:… virtual void Grow(double growBy = 1) { Plant::Grow(growBy) ; _numFlowers += size_t(growBy) ; }…};
Upgrading FlowerPlant properly
Now the following works as expected too:Plant* myPlants[5];myPlants[0] = new FlowerPlant(roseBush);myPlants[1] = new Plant();myPlants[0]->Grow();myPlants[1]->Grow();
In line 5, Grow of Plant is called In line 4, Grow of FlowerPlant is called
In both cases, the code looks at the object at run-time and calls the appropriate implementation for the object
Note that the call still has to be legal at compile time – only functions that are declared in Plant can be called via pointer to Plant
Only the implementation is selected at run-time
Generalizing further
Suppose that we want to maintain a garden
It may contain Plants (in particular, FlowerPlants), Butterflies, and even Squirrels
We want to keep them all in a single vector
We want to be able to handle the life cycle of all living things in our garden in the same way: To tell them to grow periodically To check if they are dead
Generalizing further We define a base class LivingThing with abilities Grow and
IsAlive Plants (in particular, FlowerPlants), Butterflies, and
Squirrels will inherit from it Our data structure is a vector of pointers to LivingThings: std::vector<LivingThing*> garden;
Now we can write (for example):
for (std::vector<LivingThing*>::iterator cit = garden.begin(); cit != garden.end(); cit++){ (*cit)->Grow(1); // age by a month if (! (*cit)->IsAlive()) { // do cleanup }}
Generalizing further
But how do we define the base class LivingThing?
It has to declare the functions Grow and IsAlive
But it cannot implement them – there is no meaningful implementation common to all living things!
We declare the functions as pure virtual:
class LivingThing {public: virtual void Grow(double growBy = 1) = 0; virtual bool IsAlive() const = 0;};
Generalizing further The pure virtual functions make the LivingThing class
abstract – there can be no objects which actual class is LivingThing, it’s just a common way to look at objects of derived classes.
The opposite of abstract is concrete – a concrete class is one that can be instantiated
In order to be concrete, derived classes need to override Grow and IsAlive with appropriate implementations
A pure virtual function is a placeholder – it defines the common interface without actually implementing it
The implementation will be given by inheriting classes
Generalizing further - Plant
class Plant : public LivingThing {public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { }
Plant(bool withLeaves=true,double theHeight = 1): hasLeaves( withLeaves ), height (theHeight) { }
virtual void Grow(double growBy = 1) { height += growBy ; } virtual bool IsAlive() const { return true;}private: bool hasLeaves;
double height;};
Note that we don’t need to change anything in
FlowerPlant!