Presentation is loading. Please wait.

Presentation is loading. Please wait.

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

Similar presentations


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

1 Inheritance

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

3 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?

4 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)

5 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();

6 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 Plant fields Plant interface Flower Plant fields Flower Plant interface 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

7 Destructing FlowerPlant What happens when roseBush goes out of scope? The destructor of FlowerPlant is called Since we didn’t define one, the compiler created it automatically – as: ~FlowerPlant() {} Does it do anything? Yes, it does!

8 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 class 3. 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

9 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

10 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 be void TakeGoodCare(Plant& p) {…} And then there is no copy and no slicing – function operates on the given object directly

11 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?

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

13 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?

14 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!

15 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?

16 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!

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

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

19 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

20 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 

21 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 garden; Now we can write (for example): for (std::vector ::iterator cit = garden.begin(); cit != garden.end(); cit++) { (*cit)->Grow(1); // age by a month if (! (*cit)->IsAlive()) { // do cleanup }

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

23 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

24 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!


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

Similar presentations


Ads by Google