Inheritance and Overloading Engineering H192 Winter 2005 Inheritance and Overloading Instructor notes: This lecture covers class inheritance, function overloading, function templates, and operator overloading. Winter Quarter Lecture 28
Engineering H192 Winter 2005 Inheritance Objects are often defined in terms of hierarchical classes with a base class and one or more levels of classes that inherit from the classes that are above it in the hierarchy. For instance, graphics objects might be defined as follows: Instructor notes: Here we begin a brief introduction to inheritance in C++. Inheritance makes it possible to establish a hierarchy of classes where classes that are lower in the hierarchy automatically take on the characteristics of the classes above. So, as we’d expect, the most general class in the hierarchy is at the top and as you proceed downward, the classes become more specialized. This slide indicates this by starting with a very general shape class and progressing downward to more specialized, first with the number of dimensions and then to even more specialized shapes within one of the two dimensional classes. Winter Quarter Lecture 28
Inheritance (continued) Engineering H192 Winter 2005 Inheritance (continued) This hierarchy could, of course, be continued for more levels. Each level inherits the attributes of the above level. Shape is the base class. 2-D and 3-D are derived from Shape and Circle, Square, and Triangle are derived from 2-D. Similarly, Sphere, Cube, and Tetrahedron are derived from 3-D. Instructor notes: As this slide points out, the hierarchy in our shape example could be extended even further down, as well as horizontally. We could come up with more 2D and 3D shapes and more specific examples of some of the shapes supplied here. For example, underneath Triangle we might have classes for Equilateral, Isosceles, and Right triangles. We could consider replacing Square with “Four Sided” and underneath the Four Sided class having Square, Rectangle, Trapezoid, etc. So, we can see that depending on the need, the hierarchy can be extended both vertically and horizontally. The main thing to reemphasize is that classes inherit the characteristics of their “parent” or “base” classes. Classes that are one level above another class are said to be “direct base classes” of those immediately below. Classes that are more than one level above are “indirect base classes”. Hence 3D is a direct base class to Cube and Shape is an indirect base class to Cube. How exactly we do the inheriting in C++ is briefly introduced on the next slide and with an example after that. Winter Quarter Lecture 28
Inheritance (continued) Engineering H192 Winter 2005 Inheritance (continued) class A : base class access specifier B { member access specifier(s): ... member data and member function(s); } Valid access specifiers include public, private, and protected Instructor notes: Here is the basic syntax for deriving a class A from a base class B. You begin with the keyword class followed by the name of the derived class, a colon, the base class access specifier (which will probably be public, but could be private or protected), and finally the name of the base class. Inside the derived class you will include the member data and member functions that are specific to the derived class as well as the member access specifiers as needed. These will probably be public for member functions and private or protected for member data. If you want further derived classes to have access to the member data in the derived class A, then you will want to use the protected member access specifier for the data in A. If not, then you will probably use the private member access specifier for data members in A. Winter Quarter Lecture 28
Public Inheritance class A : public B Engineering H192 Winter 2005 Public Inheritance class A : public B { // Class A now inherits the members of Class B // with no change in the “access specifier” for } // the inherited members public base class (B) public members protected members private members derived class (A) public protected inherited but not accessible Instructor notes: When specifying the inheritance of a derived class from a base class, we have three options public, protected, and private. Here we look at public inheritance. This is the most common form of inheritance. When we refer to public inheritance here we are referring to the base class access specifier (the word immediately to the left of the base class name). This is different from the access specifier for the data members. So, when we do a public inheritance of derived class A from base class B, the members that were public in B are inherited as public in A. The members that were protected in B are inherited as protected in B. And, those that were private in B, are inherited as private in A but they are not accessible because they are already private in B. So, inherited members that are private in the base class will not be accessible via member functions in the derived class. That means, the only way for the derived class to access an inherited member that was private in the base class is for there to be a public member function in the base class B that accesses any private members in B. It is for this reason that we have the protected specifier. Winter Quarter Lecture 28
Protected Inheritance Engineering H192 Winter 2005 Protected Inheritance class A : protected B { // Class A now inherits the members of Class B // with public members “promoted” to protected } // but no other changes to the inherited members protected base class (B) public members protected members private members derived class (A) protected inherited but not accessible Instructor notes: Here we look at protected inheritance. In this case, members that were public in the base class are “promoted” to the more restricted access specifier state of protected in the derived class. The protected inherited members retain their original access level in the derived class. Winter Quarter Lecture 28
Private Inheritance class A : private B Engineering H192 Winter 2005 Private Inheritance class A : private B { // Class A now inherits the members of Class B // with public and protected members } // “promoted” to private private base class (B) public members protected members private members derived class (A) private inherited but not accessible Instructor notes: Finally we look at private inheritance. In this case, members that were public or protected in the base class are “promoted” to the more restricted access specifier state of private in the derived class. This means that these members will be accessible by member functions of the derived class, but they will not be accessible in another derived class, that is one or more below A in the hierarchy. Again, base class members that were already private are private in the derived class, but they are not accessible by member functions of the derived class A because they are already private to the base class B. Winter Quarter Lecture 28
Inheritance (continued) Engineering H192 Winter 2005 Inheritance (continued) class Shape { public: int GetColor ( ) ; protected: // so derived classes can access it int color; }; class Two_D : public Shape // put members specific to 2D shapes here class Three_D : public Shape // put members specific to 3D shapes here Instructor notes: Here we see a quick example of making use of inheritance based on our Shape class and its derived classes. Note that Shape has a public member function and a protected data member. If the data member color were private, rather than protected, then it would not be accessible from the derived classes Two_D, Three_D, Square, Cube and any other derived classes that might be created. Both Two_D and Three_D inherit all of the data members and member functions that Shape contains while also, presumably, having functions and data that are specific to each of them. Winter Quarter Lecture 28
Inheritance (continued) Engineering H192 Winter 2005 Inheritance (continued) class Square : public Two_D { public: float getArea ( ) ; protected: float edge_length; } ; class Cube : public Three_D float getVolume ( ) ; Instructor notes: On this slide we have the derived classes Square and Cube which are derived from Two_D and Three_D respectively. Again the data members in these derived classes are protected so that classes further derived from Square and/or Cube would be able to access the edge_length member through the further derived classes’ member functions. A point to reemphasize...Square, for example, has all of the characteristics of Two_D as well as all of the characteristics of Shape without having to specify them explicitly in the class definition. Winter Quarter Lecture 28
Inheritance (continued) Engineering H192 Winter 2005 Inheritance (continued) int main ( ) { Square mySquare; Cube myCube; mySquare.getColor ( ); // Square inherits getColor() mySquare.getArea ( ); myCube.getColor ( ); // Cube inherits getColor() myCube.getVolume ( ); } Instructor notes: Now, inside the main () program we declare two objects, mySquare which is of class Square and myCube which is of class Cube. Because of inheritance, we can call the getColor () function from mySquare as well as from myCube, even though getColor () was defined in the Shape class. In addition we can call the getArea () and getVolume () functions from the derived Square and Cube objects, respectively. Winter Quarter Lecture 28
Engineering H192 Winter 2005 Function Overloading C++ supports writing more than one function with the same name but different argument lists. This could include: different data types different number of arguments The advantage is that the same apparent function can be called to perform similar but different tasks. The following will show an example of this. Instructor notes: Overloading is the ability to have multiple functions with the same name, but with different signatures. A function’s signature is a combination of its name and its argument list. So, if two functions have the same name, but have a different number of arguments, or different types of arguments, or even the same types, but in a different order such as float, float, int is different than float, int, float, then those two functions have different signatures and can be overloaded. The motivation behind overloading is to be able to use similar function calls to functions that essentially do the same thing, but process different data types. For example if we had a function that compared two integers to see which was larger and wanted to be able to do the same comparison on two floats, then we could overload the function. We would write two functions with the same name, but different signatures. One important note, the return type is not part of the function signature. So, if you’re overloading a function, the return type cannot be the only difference between two versions of the overloaded function. Winter Quarter Lecture 28
Function Overloading Winter Quarter void swap (int *a, int *b) ; Engineering H192 Winter 2005 Function Overloading void swap (int *a, int *b) ; void swap (float *c, float *d) ; void swap (char *p, char *q) ; int main ( ) { int a = 4, b = 6 ; float c = 16.7, d = -7.89 ; char p = 'M' , q = 'n' ; swap (&a, &b) ; swap (&c, &d) ; swap (&p, &q) ; } Instructor notes: Here we see a quick example of function overloading using the swap () routine. We’ve got three different prototypes for swap (). The first expects integer pointers, the second expects float pointers, and the third expects char pointers. In our main () routine we initialize pairs of ints, floats, and chars. Then we call swap () with the only difference between the calls being the argument types. Therefore the signature of the swap () being called each time is different, and the compiler knows which version of swap to call. On the next slide we see the different overloaded versions of swap (). Winter Quarter Lecture 28
Function Overloading void swap (int *a, int *b) Engineering H192 Winter 2005 Function Overloading void swap (int *a, int *b) { int temp; temp = *a; *a = *b; *b = temp; } void swap (float *c, float *d) { float temp; temp = *c; *c = *d; *d = temp; } void swap (char *p, char *q) { char temp; temp = *p; *p = *q; *q = temp; } Instructor notes: Here are our three different versions of the swap routine. Each expects pointers to variables, but the first expects pointers to ints, the second expects pointers to floats, and the last expects pointers to chars. When the calls to the swap function are made, as in main (), the compiler matches up the signature of the function being called with the function definition that corresponds to it and calls the correct version. So, in the main () routine here the calls to the functions look very similar, with the only difference being the types of arguments. Winter Quarter Lecture 28
Engineering H192 Winter 2005 Function Templates We have discussed overloaded functions as a way to perform similar operations on data of different types. The swap functions were an example. We wrote three functions with the same name but different data types to perform the swap operations. Then we could call swap (&a, &b), for example, and C++ would select which function to use by matching the data type of a and b to one of the functions. Instructor notes: Beginning with overloaded functions is a good way to move into function templates. In some ways this made the programming simpler because all we had to do was be concerned with one function name and knowing that by passing the correct arguments, and thus determining the correct function signature, our program would use the version of the function that we wanted. This is easier than having to use a function with a different name but essentially same functionality, but we still had to write multiple versions of the function. Templates allow us to write one version of the function and then the compiler will create the multiple versions for us automatically based on the need, that is the different signatures that are used to call the template function. Winter Quarter Lecture 28
Engineering H192 Winter 2005 Function Templates Another way to perform this task would be to create a function template definition. With a function template defined, when we call swap (&a, &b), C++ will generate the object code functions for us. The program on the following slides is an example. Instructor notes: To reiterate...a function template will set up the framework for the functions we need, and then based on how the template is called, the compiler will generate the necessary code to deal with the different situations or, in other words, the different signatures. Winter Quarter Lecture 28
Function Templates template <typename T> void swap (T *a, T *b) Engineering H192 Winter 2005 Function Templates template <typename T> void swap (T *a, T *b) { T temp; temp = *a; *a = *b; *b = temp; } T is a “dummy” type that will be filled in by the compiler as needed a and b are of “type” T temp is of “type” T swap is a function template, NOT a function Instructor notes: Here we have a quick example of writing a template for our swap () function. This slide appears in two parts. First the template is displayed, and then some explanatory text appears. It might be a good idea to through it once quickly pointing out the syntax of the first line and then click again to get the explanation to appear. The keyword template tells the compiler that this is a function template which the compiler will use to create the function definitions when it sees instances of swap () being called. The compiler will look at the argument types in the calls to swap () and then substitute those types in appropriately using the template to generate the necessary swap () functions with the appropriate types. Hence, every where a T occurs in the template, the compiler will substitute the correct type depending on the call to the function. One thing that is not obvious and definitely will not be to students seeing this for the first time. The void return type in front of swap is particular to this case. void is not a required part of a template as a return type. If swap () were to return an int in all cases, then the void would be replaced by int. Winter Quarter Lecture 28
Function Templates int main ( ) { int a = 5, b = 6; Engineering H192 Winter 2005 Function Templates int main ( ) { int a = 5, b = 6; float c = 7.6, d = 9.8; char e = 'M', f = 'Z'; swap (&a, &b); // compiler puts int in for T swap (&c, &d); // compiler puts float in for T swap (&e, &f); // compiler puts char in for T cout << "a=" << a << " and b=" << b << endl; cout << "c=" << c << " and d=" << d << endl; cout << "e=" << e << " and f=” << f << endl; } Instructor notes: So, for this example of templates we have a simple main () routine that makes us of the swap () template we just created. Unlike the overloading example where we wrote three different swap () routines, each with the same name, but a different signature, this time we have one template routine and let the compiler do the work of putting in the correct data types based on the signature of the function call itself. Winter Quarter Lecture 28
Engineering H192 Winter 2005 Operator Overloading C++ already has a number of types (e.g., int, float, char, etc.) that each have a number of built in operators. For example, a float can be added to another float and stored in yet another float with use of the + and = operators: floatC = floatA + floatB; In this statement, floatB is passed to floatA by way of the + operator. The + operator from floatA then generates another float that is passed to floatC via the = operator. That new float is then stored in floatC by some method outlined in the = function. Instructor notes: Here we look briefly at operator overloading, which is very much like function overloading. And, in fact, operators are just a very specific type of function. We’ve already seen plenty of examples of how various operators work. The “+” and the “+” are two common operators. As might be expected, we can take these common operators and overload them so that depending on their operands, the operators will take action that is specific to those particular operands. Bear in mind it is a good idea for the overloaded operation to work in a similar fashion to the original. Operator overloading enables you to apply standard operators to objects of your own data types. Winter Quarter Lecture 28
Operator Overloading (continued) Engineering H192 Winter 2005 Operator Overloading (continued) Operator overloading means that the operators: Have multiple definitions that are distinguished by the types of their parameters, and When the operator is used, the C++ compiler uses the types of the operands to determine which definition should be used. Instructor notes: Operator overloading enables you to apply standard operators to objects of your own data types. Doing so requires writing a function that redefines a particular operator so that it performs a particular action every time it is applied to objects of your class. Operator overloading doesn’t allow you to invent new operators. In addition, you can’t change the precedence of an operator or the number of operands. Winter Quarter Lecture 28
Operator Overloading (continued) Engineering H192 Winter 2005 Operator Overloading (continued) A programmer has the ability to re-define or change how the operators (+, -, *, /, =, <<, >>, etc.) work on their own classes. Overloading operators usually consists of defining a class member function called operator+ (where + is any operator). Note that operator is a reserved word in C++. If anything usually follows that operator, it is passed to the function. That function acts exactly like any other member function; it has the same scope as other member functions and can return a value just like any other member function. Instructor notes: A side note, the function that overloads an operator for a particular class does not necessarily have to be a member of the class...it can be an ordinary function. Winter Quarter Lecture 28
Operator Overloading (continued) Engineering H192 Winter 2005 Operator Overloading (continued) Steps for defining an overloaded operator: 1. Name the operator being overloaded. 2. Specify the (new) types of parameters (operands) the operator is to receive. 3. Specify the type of value returned by the operator. 4. Specify what action the operator is to perform. Instructor notes: Winter Quarter Lecture 28