24
Inheritance

Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

Embed Size (px)

Citation preview

Page 1: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

Inheritance

Page 2: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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;

};

Page 3: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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?

Page 4: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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)

Page 5: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } 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();

Page 6: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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

Page 7: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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!

Page 8: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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

Page 9: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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

Page 10: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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

Page 11: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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?

Page 12: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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);}

Page 13: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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?

Page 14: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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!

Page 15: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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?

Page 16: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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!

Page 17: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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 ; } …};

Page 18: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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) ; }…};

Page 19: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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

Page 20: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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

Page 21: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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 }}

Page 22: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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;};

Page 23: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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

Page 24: Inheritance. Recall the plant that we defined earlier… class Plant { public: Plant( double theHeight ) : hasLeaves( true ), height (theHeight) { } Plant(

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!