Introduction to C++ Introduction to C++ Dr Alex Martin 2013 Slide 1
Inheritance Consider a new type Square. Following how we declarations for the Rectangle and Circle classes we could declare it as follows: class Square{ double side; public: // // constructors and destructor // Square(double s):side(s){} ~Square(){}; // double Area() const; void Print() const; }; Introduction to C++ Dr Alex Martin 2013 Slide 2
Inheritance This works, but is somewhat unsatisfactory, because it means that we have to declare and define functions for a completely new class, whilst we know that a Square shares much in common with a Rectangle. In fact we can consider a Square as a special type of Rectangle. Similarly whilst Circles are not Rectangles (or vice versa) both can be considered specializations of some more general Shape class. These relationships may be illustrated in follows: Introduction to C++ Dr Alex Martin 2013 Slide 3
Inheritance Introduction to C++ Dr Alex Martin 2013 Slide 4
Inheritance Notice that in spoken English these relationships would be typically indicated by the phase "is a". In OO terminology, Shape is said to be a base class (superclass) of Rectangle (and Circle and Square). Square is said to be a derived class (subclass) of Rectangle. Recognition of such inheritance relationships typically allows one to simplify problems. There are several benefits: Allows common code to be associated with a base class and therefore kept common. Allows all objects of related classes (i.e. those that share a common base class) to be treated in a common way e.g. treat Circles,Rectangles and Squares as shapes. Introduction to C++ Dr Alex Martin 2013 Slide 5
Inheritance How to Implement Inheritance in C++ To declare a class Square derived from class Rectangle one uses the follow syntax: class Square: public Rectangle{ public: // // constructors and destructor // Square(double s):rectangle(s,s){} ~Square(){}; // void Print() const; }; Introduction to C++ Dr Alex Martin 2013 Slide 6
Inheritance Such an class is illustrated in the program square.cpp. Here the public Rectangle declaration specifies that the Square class is derived from Rectangle. Notice how the constructor for the Square class uses the constructor for the Rectangle class to initialize its private data. The Square(double s):rectangle(s,s) syntax is an extension of the syntax used to initialize data members. In general a derived class will inherit all of the members (both data and functions) of its base class and usually add some more specialized functions of its own. This means, for example, that objects of type Square may use the function Area() which is declared in Rectangle In some cases the member functions declared in the base class are inappropriate for a derived class, for example, in this case of the Square class we would not want to use the Print function derived in the Rectangle base class. In order to prevent this it is possible to override a method (function) in a base class, by providing a replacement version in a derived class i.e. Square::Print() Introduction to C++ Dr Alex Martin 2013 Slide 7
Inheritance Protected Members When we first introduced classes we discussed public and private member access. With the use of inheritance, a third access qualifier, protected, is relevant. protected members are exactly like private members except they are accessible in member functions of a derived class. N.B. private members are NOT accessible in a derived class. Introduction to C++ Dr Alex Martin 2013 Slide 8
Inheritance Pointers and References to Inherited Class Objects When we have talked about pointers to class objects we noted that pointers to objects were specific in their type. This is also true for references With objects of inherited class types certain pointer conversions are legal. In particular, if one has an object of a derived class then it is perfectly legal (and often useful) to construct a pointer (or reference) to a base type referring to it. For Example, Square sq(10.0);...... Rectangle* p = &sq; Rectangle& r = sq; This good and legal code because a Square is a Rectangle The pointer or reference will behave just like any other Rectangle pointer or reference. This is one of the few places in C++ where pointer type casts are valid (and safe). Introduction to C++ Dr Alex Martin 2013 Slide 9
Inheritance N.B. Alhought one can generally convert a pointer (or reference) to a derived class to a base class. The reverse is not possible. In general it is NOT legal to convert a pointer (or reference) to a base class object to a pointer (or reference) of a derived class object. This is because e.g. not all Rectangles are Squares. Introduction to C++ Dr Alex Martin 2013 Slide 10
Inheritance The nice thing about accessing objects via pointers or references of their base class type is that one may group together related objects and treat them in a similar way. e.g. Put pointers to them in a linked list or array. So for example, if we declare an array of pointers-to-rectangles, we can store a collection of different objects. Square objects (and any other class derived from Rectangle) can be stored via pointers. Rectangle* p[4];...... p[0] = new Rectangle(10.0,20.0); p[1] = new Square(10.0); Introduction to C++ Dr Alex Martin 2013 Slide 11
Inheritance We may also write functions which have a pointer or reference to a base type as a argument and use them with derived types. Draw( Rectangle& shape );...... Rectangle r(10.0,20.0); Square s(10.0);...... Draw(r); Draw(s); Introduction to C++ Dr Alex Martin 2013 Slide 12
Polymorphism Polymorphism and Virtual Functions There is complication with referring to objects via pointers to their base class type. Suppose I assign a pointer-to-a Square to a pointer-to-a Rectangle: Square sq(10.0); Rectangle* p = &sq;... p -> Print(); The function call p -> Print() uses the function Rectangle::Print() rather than Square::Print(). This is demonstrated in the example squarep.cpp This is not always what one wants! Introduction to C++ Dr Alex Martin 2013 Slide 13
Polymorphism As usual C++ provides a solution. This is to convert Rectangle (and Square) into so called polymorphic types using virtual functions. If the function Print() is declared virtual in the Rectangle base class: class Rectangle{ double width; double height; public: // // constructors and destructor // Rectangle(double w, double h):width(w),height(h){} virtual ~Rectangle(){}; // destructor // double Area() const; virtual void Print() const; }; Introduction to C++ Dr Alex Martin 2013 Slide 14
Polymorphism With such a class declaration, the Square class can still be derived in the same way. However, if a pointer-to-a Rectangle (p) is now assigned the address of a Square object then p -> Print() will call Square::Print(). The program sqarrayv.cpp demonstrates this. N.B. notice that the destructor is also declared virtual, it is a good idea to always do this for polymorphic types. Such polymorphic types retain run-time information which allow their correct identity to be preserved, even if they are referenced via a pointer type. (Usually achieved by storing in class objects a special pointer to the so-called virtual function table (v-table) ) Introduction to C++ Dr Alex Martin 2013 Slide 15
Polymorphism Pure Virtual Functions and Abstract Classes Sometimes it is very useful to define an abstract base class which defines a set of interface functions to a hierarchy of object types, but which it makes no sense to instantiate (actually create objects of the type). Such classes can be declared by specifying some (or all) of the member functions to be pure virtual. e.g. the Shape class needs to declare interface functions Area and Print but is an abstract class that one doesn't actually create objects of. A suitable declaration would be: class Shape{ public: Shape(){} virtual ~Shape(){} virtual double Area() const = 0; virtual void Print() const = 0; }; The = 0 syntax on a virtual function specifies it is pure Introduction to C++ Dr Alex Martin 2013 Slide 16
Polymorphism Whilst such Shape can not be directly created. concrete classes such as Rectangle and Circle can be derived from Shape and used to instantiate objects providing they override and implement all of the pure virtual functions in their base class. Importantly it is still valid to use a pointer-to-a Shape type to refer to any of the polymorphic types derived from Shape, and to call its virtual functions. Introduction to C++ Dr Alex Martin 2013 Slide 17
Introduction to C++ Templates One key C++ advanced feature is the concept of Templates. Templates provide direct support for generic programming, that is programming using types as parameters The C++ template mechanism allows a type to be a parameter in the definition of a function or a class. A function template is useful when one want to be able to define a function which works in the same way for many different, possibly unrelated, types (where a type is one of the built in types such as int or a class). A templated class is generally used when one needs to define a container class which is defined for objects of different types. Such containers could be things like linked lists and stacks Introduction to C++ Dr Alex Martin 2013 Slide 18
Introduction to C++ Templates Function Templates Consider the function: int max ( int x, int y ){ if ( x > y ){ return x; } else { return y; } } Introduction to C++ Dr Alex Martin 2013 Slide 19
Introduction to C++ Templates This requires its input arguments are of type int if we wanted to have a similar function which works of another type, double say, we would have to define it as well double max ( double x, double y ){ if ( x > y ){ return x; } else { return y; } } This would work, but it would clearly be silly if had to define many such identical functions working on different types. This is where templated functions are useful. Introduction to C++ Dr Alex Martin 2013 Slide 20
Introduction to C++ Templates Instead of defining two or more such type dependent functions, we may provide the following function template: template <class T> T max ( T x, T y ){ if ( x > y ){ return x; } else { return y; } } This template definition is defining a parameter T which can represent any possible type (including classes). Given this definition the compiler will automatically be able to build versions of the max which will find the maximum of two variables of type int, float, char etc. In fact it will work for any type/class for which the operator> is defined. Introduction to C++ Dr Alex Martin 2013 Slide 21
Introduction to C++ Templates Once we have defined the templated function we can use members of the family of functions in the same way as we would if they had been defined in the traditional way as show in the example max.cpp cout << " max ( 1, 2 ) = " << max(1,2) << endl; cout << " max ( 1.0, 2.0 ) = " << max(1.0,2.0) << endl; cout << " max ( 'a', 'z' ) = " << max('a','z') << endl; The complier builds each of the different functions which are used in the program. Introduction to C++ Dr Alex Martin 2013 Slide 22
Introduction to C++ Templates Class Templates Class templates allow one to define data types which act as containers which can store other types in a generic way. To illustrate the use of a class template, we consider the implementation of a stack container. A stack is a simple data structure in which elements are added and removed from its top using a First-in Last-out formalism. A stack needs to have two functions defined push which adds an element to the top of the stack and pop which removes the element of the top of the state. A stack pointer (not necessarily a C++ pointer) keeps track of the top of the stack. Introduction to C++ Dr Alex Martin 2013 Slide 23
Introduction to C++ Templates The diagram shows a stack in which the following actions are taken: A number 1 is Pushed onto the stack A number 2 is Pushed onto the stack The last number 2 is Popped off the stack A number 3 is Pushed onto the stack Introduction to C++ Dr Alex Martin 2013 Slide 24
Introduction to C++ Templates The following definition taken from the example stack.cpp declares a generic stack class using a template. template < class T > class Stack { int size; // max elements in stack int top; // location of top element T* stackptr; // pointer to the stack public: Stack ( int _size=10 ); // ctor ~Stack(); // dtor int push( const T& ); // push element onto stack int pop( T& ); // pop element off stack }; Introduction to C++ Dr Alex Martin 2013 Slide 25
Introduction to C++ Templates With such a class one can declare and use a Stack on which ints can be stored. Stack< int > mystack(10); // Declare a stack to hold ints int i; mystack.push(1); // push 1 onto stack mystack.push(2); mystack.pop(i); // pop last entry off of stack mystack.push(3); Introduction to C++ Dr Alex Martin 2013 Slide 26
Introduction to C++ Templates And with the same templated class we can use a Stack on which doubles can be stored Stack< double > mystack(10); mystack.push(1234.567); This should work for any class for which sensible copy constructor has been defined. Introduction to C++ Dr Alex Martin 2013 Slide 27