Warm-Up & Hands-On: L L L L L
Warm-Up & Hands-On: L L L L L
Start now to use STL... (crucial) ! #include <algorithm> : copy, sort, min_element, max_element ! #include <string> : string ! #include<vector>, #include<set>, #include <map> ! #include <fstream>: istream, ostream ! http://www.cplusplus.com/reference/
! many,
fine-grained iterations: extend XOR refactor the code (doesnt compile) fix compilation problems (doesnt run properly) fix bugs (is not ready to be extended) the crude truth... ...it is not the computer. Humans are sloppy.
! accept
Today:
key element of OO programming is inheritance: A new class can inherit the data members and member functions of a previously defined class (and add new ones).
! Example:
ETH-Members, Student, Professor, Assistant Students, professors, and assistants are ETH-Members.
! Example:
work out an example with base class Employee, and derived class Manager. ! Important: Keep in mind that every Manager is an Employee but not every Employee is a Manager. An object of a derived class can be treated as an object of the corresponding base class. ! A derived class cannot access the private members of its base class. ! A derived class constructor can always call the constructor for its base class.
Employee
Manager
A derived class
class Manager : public Employee { public: The base class constructor is called explicitly Manager(string name, string dep) : Employee(name), department_(dep) {}; void print_info() const { Base class function call Employee::print_info(); cout << "Head of the " << department_ << endl; } An additional (manager specic) private data member private: string department_; };
A client
int main() { string employee_name = "Simpson"; Employee worker(employee_name); string boss_name = "Mr. Burns"; string comp_name = "Nuclear Power Plant"; Manager boss(boss_name, comp_name); worker.print_info(); boss.print_info(); // Name: Simpson // Name: Mr. Burns Head of the Nuclear Power Plant
Employee* base_ptr = &boss; base_ptr is a pointer variable to an object of class Employee and its value is the address of the boss object. This is correct, since every object of type Manager can be treated as an Employee. The variable base_ptr is called a base class pointer. functions and data members can be addressed using the dot notation, but for pointers the arrow notation is more convenient: worker.print_info(); base_ptr->print_info(); (*base_ptr).print_info();
! Member
// equivalent
The data type of the pointer determines which objects member function is called NOT the data type of the object the pointer currently points to!
Employee a; a.print_info(); Manager b; cout << b.get_name(); b.print_info(); b.Employee::print_info(); Manager c; Employee* ptr = &c; ptr->print_info();
What if we want the member function of the object actually pointed to to be called?
Encapsulation
in C++: Classes, Objects and Access Specifiers (private, public, ...)
Hierarchical Classification
in C++: Subclasses and Inheritance
Polymorphism
in C++: Overloading and Overriding, virtual functions and abstract base classes
Polymorphism
Types of Polymorphism
Compile-time
(early binding)
(late binding)
in C++: virtual functions, pure virtual functions, base class pointers, abstract base classes
Ingredients of Polymorphism
Ingredient #1:
A pointer to the base class can point to any object of derived sub-classes. (remember: if A is a subclass of B, then A is a B.)
Base_class *p; // pointer to object of class Base_class Base_class base_obj; // object of class Base_class Sub_class sub_obj; // object of a sub-class p = &base_obj; p = &sub_obj; // p points to base class object // p points to sub-class object
Any inherited elements in the sub-class can be accessed through the pointer. Elements specific to the sub-class cannot!
#include <iostream> #include <string> class B_class { char author[80]; public: void put_author(char *s) { strcpy(author, s); } void show_author() { cout << author << endl; } }; main() { B_class *p; // pointer to class B_class B_obj; p = &B_obj; // let p point to object // access member function through pointer: p->put_author(William Shakespeare); p->show_author(); // WHAT WILL THIS PRINT ? }
Elements only defined in the sub-class can not be accessed through the base class pointer. The increment operator will not work correctly if the base class pointer actually points to an object of a sub-class. => ..dangerous, TIP OF THE ICEBERG !
Let both the base class and its sub-class have the same function declaration but with different implementations. A base class pointer can point to an object of either class. If the function is called through such a pointer, which implementation is executed?
Ingredients of Polymorphism
Ingredient #2:
Virtual functions
Virtual functions
If a function is declared as virtual, each derived class can have its own implementation of it. When called through a base class pointer, the class of the object pointed to determines which version of the function is called We thus have run-time polymorphism. A class that includes a virtual function is called a polymorphic class. A class that inherits from a polymorphic class is automatically also polymorphic.
pointer to base class let p point to base object will print Base class let p point to derived object will print Derived class
Overloaded functions have different declarations (argument lists). Virtual functions must have exactly the same declarations. Redefining a virtual function in a derived class is called Overriding. Virtual functions that have different declarations lose their polymorphic nature and are simply overloaded. careful with const...
The virtual attribute is inherited. Once a function is declared virtual, it remains virtual, no matter how many layers of derived classes it has been passed through. The virtual keyword does not have to be repeated in the derived sub-classes. If a sub-class does not override a virtual function, the implementation of the closest higher class will be used.
Functions that will be common to all subclasses can be declared by the base class in a single place. The base class dictates the interface for all derived classes, but the latter can have their own actual implementations. (One interface, multiple methods) Helps to develop complex programs or libraries since one can be sure that all derived classes can be accessed using the same function interface (even if they do completely different things).
// base class pointer to Shape // on object of class Triangle // on object of class Rectangle
if (user_wants_triangle) { p = &t; // let pointer point to Triangle t } elseif (user_wants_rectangle) { p = &r; // point to Rectangle r } p->set_dim(10.0, 5.0); // define dimensions of shape. p->show_area(); // print area of shape (whatever it is) return 0;
Where is the problem ? User does not know whether to pass the radius as the first or the second argument.
Call Circle::set_dim with repeated first and second argument. => Bad idea as it violates the philosophy of Polymorphism Give the second argument a default value in the base class:
// in Shape replace set_dim with: void set_dim(double i, double j=0) // in main one can then simply do: Circle c; p = &c; p->set_dim(9.0); p->show_area();
Friday, May 3, 2013
Another Problem
what happens if someone defines a sub-class of Shape and forgets to implement the show_area() function?
Another Problem
class Shape { protected: double x, y; // dimensions of shape public: void set_dim(double i, double j) { x = i; y = j; } // set dimensions virtual void show_area() { // print area // not defined in base class, since type of shape // is unknown here! Still we declare the function // to make assure that all shape sub-classes will // have this method. cout << Not defined. << endl; } };
Its object will print Not defined since it inherits the virtual function from the base class.
Another Problem
The solution: Force the sub-class to implement the corresponding function by declaring it pure virtual.
A pure virtual function has no definition in the base class and all derived classes inheriting it are forced (otherwise the program will not compile) to implement it using exactly the given interface. Syntax:
virtual return-type function-name(argument-list) = 0;
A class that contains at least one pure virtual function is called an abstract base class.
No objects can be instantiated from an abstract base class (since function implementations lack). Sub-classes derived from it need to implement all pure virtual functions. Objects can only be instantiated from sub-classes of it. Base class pointers can still be declared on abstract base classes (but not initialized!):
Abstract_base a; Abstract_base *p; Derived b; p = &a; p = &b; // ERROR // OK // NOT OK // OK
Every particular sub-class defining an actual shape now has to implement the show_area function.
Friday, May 3, 2013
Advise
Use run-time polymorphism (late binding) whenever meaningful (typically only the case in sufficiently large and complex programs), but not by default. The reason is that late binding is slower than early binding and your program will lose performance (compiler cannot optimize as decision is taken only at runtime). In other words: Use the power, but dont abuse it. (but your only goal is: cut the development time)
Friday, May 3, 2013
" A class is made abstract by declaring one or more of its members pure virtual. " virtual return-value function() = 0; // pure virtual " You cannot instantiate an object of an abstract class, but you can derive a new class and provide a definition for pure virtual functions. " Abstract classes are very useful to specify interfaces. All functions in the hierarchy must use the same interface, and provide their own implementation.
Generic Programming
If we implement an algorithm, we have to write some sort of source code and all the variables will be of a specific data type. Example: An implementation of the Quicksort algorithm in C could for example use real variables. This implementation will then only be able to sort sequences of real values.
This wastes one of the most precious features of an algorithm: its generality!
In practice we will end up writing the same algorithm several times for different data types. (and maybe we use function overloading to provide a consistent interface).
All of these implementations will be exactly the same, except for the data type specifiers. (int,
double, char, )
Use the same function implementation for different data types without having to explicitly recode specific versions for different types of data.
Generic functions
Generic functions define the general set of operations that can be applied to various data types. The specific data type the function will operate upon is passed to it as a parameter. The compiler will automatically generate the correct specific code for the type of data that is actually used. A generic function can be thought of as a function that overloads itself on demand. A specific instance of a generic function (i.e. compilergenerated for a specific data type) is called a generated function.
It defines the template of a function implementation and the compiler will automatically fill in the correct data type wherever the DataType placeholder appears. The placeholder is declared by the typename keyword.
#include <iostream> // generic abs function for any numeric data type // the placeholder data type is called X template <typename X> X abs(X value) { // return the absolute value using the // ternary ? Operator (have you seen this before?) return value < 0 ? value : value; } main() { cout cout cout cout }
abs(-10) << endl; abs(-10.0) << endl; abs(-10L) << endl; abs(-10.0F) << endl;
// // // //
return 0;
What will happen if we call our abs function with a character argument?
cout << abs(`a`) << endl;
It will just print `a` on the screen since a character is nothing but an integer between 0 and 255 (ASCII code)!
The compiler complains with something like: Wrong type argument to unary minus.
Generic functions work for all data types for which they possibly can. Even for those being added to the language later and for those you did not think about when implementing the function (but maybe some smart user of your function will utilize them anyway). Only as many overloaded version as actually used in a specific program are generated (and not all that could possibly be used). You save a lot of typing by not having to overload functions manually. Less error-prone than manual overloading (copy-paste errors). Your code is more readable, less cluttered, and more maintainable (fixes only in one place).
More than one generic data type can be defined in the template statement using a comma-separated list:
template <typename type1, typename type2> void myfunc(type1 x, type2 y) { // body }
The type-to-argument assignment in the parameter list defines how the compiler interprets function calls:
myfunc(10, hi) // type1 will be int, type2 char*
Attention: Syntax !
The template keyword and the function declaration do not need to be on the same line. This is perfectly valid:
template <typename type1> void myfunc(type1 x) { // body }
However: NO other statements must occur between the template statement and the function declaration. This will not compile:
template <typename type1> int i; // SYNTAX ERROR ! void myfunc(type1 x) { // body }
Although a generic function overloads itself you can still manually overload it explicitly if needed. The explicit overload then overwrites the generic function only relative to that specific data type version (Specialization). Hence you can accommodate exceptions for which the general algorithm provided in the generic function needs to do something slightly different.
All generated versions of a generic function are meant to perform the same action. (only the data type may differ.) If you want to have different actions performed in different versions of a function, use overloading instead.