Informatik C++ Modern and Lucid C++ for Professional Programmers part 13 Prof. Peter Sommerlad Institutsleiter IFS Institute for Software Rapperswil, HS 2015
Inheritance and dynamic Polymorphism base classes, virtual member functions, multiple inheritance Some text-only Slides for self-study will be skipped in lecture
Reasons for using Inheritance Mix-in of functionality from empty base class often with own class as template argument e.g., boost::equality_comparable<t> Adapting concrete classes (last week) (no own data members) often better to wrap adaptee as member with inheriting constructors of C++11 convenient e.g., IndexableAdapter<T> 3
Inheritance for dynamic binding Implementing a design pattern with dynamic dispatch provide common interface for a variety of dynamically changing or different implementations exchange functionality at run-time treat one or many in an identical way (Composite) Key: base class/interface class provides a common abstraction that is used by clients 4
Inheritance Syntax class Base {}; class Derived : public Base{}; In class definition after class name and a colon put the list of base classes, if any sequence is important -> sequence of initialization if multiple base classes with interface inheritance, base class must be public private inheritance is possible, but only useful for mix-in classes that provide friend functions class DerivedPrivateBase : Base {}; struct DerivedPublicBase : Base {}; 5
Syntax for multiple inheritance class Base {}; struct MixIn {}; class MultipleBases: public Base, private MixIn {}; sequence is important -> sequence of initialization if multiple base classes exist can mix public and private private inheritance can be used for some mix-in base classes that only add friend functions, like boost/operators.hpp helper classes most often, private base classes are wrong design! more in C++ Advanced module 6
Initializing Base Classes class DerivedWithCtor: public Base{ DerivedWithCtor():Base{}{} calling base class ctors put base class ctor call before member initializers in constructor's initializer list (before the body) you can not call "super()" in the body, like in Java -> there is no shorthand for referring base class and it must be fully constructed before the body runs 7
Base class ctor before member init class DerivedWithCtor: public Base{ DerivedWithCtor(int i, int j) : Base{i}, mvar{j} {} When initializing own member, base classes are initialized before compiler enforces this rule, even though you can put the list of initializers in wrong order You can also delegate to another constructor of your own class DerivedWithCtor(int i) : DerivedWithCtor{i,i} {} object fully constructed after first ctor is done 8
Inheriting and Visibility public: members of the base classes are visible and usable on derived class objects unless the derived class defines members with the same name (even with different parameters) protected: members of the base classes can be used within the derived class private: members of the base classes can not be accessed 9
Visibility Rules public: for members forming the interface of the class. usually member functions, type (aliases) to be used, and sometimes constants protected: members to be used by derived classes, if the class is designed as a base class. Often overused or used instead of private which provides better encapsulation. private: members required for the implementation and not to be used accessed by the class' users, often encapsulates member variables 10
Dynamic Polymorphism - Important? C++ default mechanisms support value classes copying/moving, deterministic lifetime Operator and function overloading and templates allow polymorphic behavior at compile time this is often more efficient, no run-time indirection Dynamic polymorphism needs object references or (smart) pointers to work syntax overhead base interface must be a good abstraction 11
When dynamic polymorphism in C++ Implementing design patterns for run-time flexibility: Strategy, Composite, Decorator client code uses abstract interface and gets parameterized/ called with reference to concrete instance but: if run-time flexibility is not required, templates can implement many patterns with compile-time flexibility as well Often (mis-)used for conceptual hierarchies: is-a relationships even if almost all introductory examples use them 12
Why inheritance can be bad Inheritance introduces a very strong coupling between subclasses and their base class you can hardly change the base class You can not get rid of your genes API of base class must fit for all subclasses very hard to get right Conceptual hierarchies are often used as examples but are usually very bad software design, e.g., animal->bird->duck Only one standard library part (the oldest) uses inheritance with dynamic polymorphism: iostreams 13
iostreams hierarchy (simplified) Programming against abstractions: abstraction for formatted input abstraction for formatted output concrete class for string input concrete class for string output concrete class for file input concrete class for file output streambuf hierarchy uses virtual member functions! 14
Potential problems with inheritance and pass-by-value Assigning or passing by value a derived class value to a base class variable/parameter incurs object slicing only base class member variables are transferred Member functions in derived classes hide base class member with the same name, even if different parameters are used can be problematic, esp. with const/non-const 15
Member Hiding Problem #include <iostream> struct Base { void foo(int i) const { std::cout << "foo(i):" << i << '\n'; Overloaded member functions in subclasses hide all parent class }; } member with the same name! struct Derived:Base { void foo() { std::cout << "Derived::foo()\n"; } }; int main(){ hides Base::foo(int) member function alternatively one can specify the overload keyword to be warned Base b{}; b.foo(42); Derived d{}; //d.foo(42); can not call Base::foo(int) on Derived objects d.foo(); } 16
Member Using Solving the problem #include <iostream> struct Base { void foo(int i) const { std::cout << "foo(i):" << i << '\n'; by first "using" the base class' members, overloads add to the set of available }; } functions struct Derived:Base { using Base::foo; void foo() { provide all Base::foo members as class members std::cout << "Derived::foo()\n"; } }; int main(){ Base b{}; b.foo(42); Derived d{}; d.foo(42); can now call Base::foo(int) d.foo(); } 17
virtual member functions Dynamic polymorphism requires base classes with virtual member functions non-virtual member functions are statically bound and not dynamically class PolymorphicBase { public: client API of abstraction with virtual member virtual void doit() { /* something*/ } }; class Implementor: public PolymorphicBase { public: void doit() { virtual can be omitted in derived class /* something else */ }; } can be marked with override, that will produce a compile error when signature differs from base class member function. IDE will also show that, IMHO override is superfluous. 18
Don't forget about const! member functions with and without const are different! you can define both overloads! a derived class providing a member function with different "const-ness" than the base class will not override the baseclass function and the same name will hide the base class 19
Calling virtual member functions Value object -> class type used determines function, regardless of virtual Reference or pointer -> virtual member of derived class called through base class reference but not for member functions that aren't virtual 20
A bad example #include<iostream> using std::cout; struct Animal { void makesound() { cout << "---\n";} virtual void move() { cout << "---\n";} Animal() { cout << "animal born\n";} ~Animal() { cout << "animal died\n";} }; struct Bird : Animal { virtual void makesound() { cout << "chirp\n";} void move() { cout << "fly\n";} Bird() { cout << "bird hatched\n";} ~Bird() { cout << "bird crashed\n";} }; struct Hummingbird : Bird { void makesound() { cout << "peep\n";} virtual void move() { cout << "hum\n";} Hummingbird() { cout << "hummingbird hatched\n";} ~Hummingbird() { cout << "hummingbird died\n";} }; #include "AnimalBirdHummingbird.h" // bad code for demonstration purpose only! int main(){ cout << "(a)----------------------------\n"; Hummingbird hummingbird; Bird bird = hummingbird; Animal & animal = hummingbird; cout << "(b)-----------------------------\n"; hummingbird.makesound(); bird.makesound(); animal.makesound(); cout << "(c)-----------------------------\n"; hummingbird.move(); bird.move(); animal.move(); cout << "(d)-----------------------------\n"; } What is the output? What is bad with this code's design? 21
Abstract Base Classes: pure virtual struct AbstractBase { virtual ~AbstractBase(){} virtual void doitnow()=0; }; pure virtual member function virtual member functions in a base class can be "pure virtual" or abstract no implementation provided and explicitly said so by = 0; Base classes with virtual members require a virtual destructor if heap allocated without shared_ptr (you shouldn't do that anyway) use shared_ptr<base> consequently for objects on the heap! -> no need to define virtual destructors 22
Using polymorphic classes provide base class reference as function parameter, e.g., print(std::ostream &) const-references also work if only const member functions are called (those without side-effects!) no need to heap-allocate subclass objects just pass them as arguments If heap allocated can not be avoided, always use std::shared_ptr<base> and initialize it from a make_shared<concretederived>(...) 23
Why the need for virtual keyword? C++'s philosophy is that you do not pay a price for something you do not specify (mostly) Member function calls depending on the dynamic type of an object requires overhead (indirection through function pointer) virtual member functions addresses are put into a table for each class and each object carries a reference to its table, often called the "vtable" result it overhead in object size and run-time for indirect function call virtual member functions can not be inlined by the compiler 24
Design Guidelines Low Coupling try to achieve a function's/class' purpose with the minimal set of dependencies inheritance is one of the strongest couplings High Cohesion a function/class should do one thing well do not combine independent functionality split classes/function that do too much Writing unit tests can help to achieve both! 25
SOLID principles for Object-Oriented Design 26
SRP - Single Responsibility Principle 27
OCP - Open Closed Principle 28
LSP - Liskov Substitution Principle 29
ISP - Interface Segregation Principle 30
DIP - Dependency Inversion Principle 31
Inheritance and dynamic Polymorphism You should only apply inheritance and virtual member functions if you know what you do do not (like the IDE) create classes with virtual members If you design base classes with polymorphic behavior, understand the common abstraction that they present do not provide too many members or too few extract from existing class(es) the base after you see the commonality arise 32
Rules for overriding virtual members Follow Liskov Substitution Principle base class states must be valid for subclass don't break invariants of base class member functions in subclass must accept at least the arguments of the base class and not return values outside the base class member function's range (co- and contravariance) don't change semantics unexpectedly 33
Summary Inheritance 3 main uses inherit features, sometimes from empty mix-in class often with CRTP, passing class as template argument of base class adapt features of a base class within a data-less subclass - just use the subclass often member better (but less convenient) dynamic polymorphism with an interface to allow run-time exchange or polymorphic behavior (implement design patterns) 34
Merry Christmas 35