תרגול מס' 11 הורשה פולימורפיזם.

Slides:



Advertisements
Similar presentations
Has-a Relationships A pen “has a” or “contains a” ball.
Advertisements

The C ++ Language BY Shery khan. The C++ Language Bjarne Stroupstrup, the language’s creator C++ was designed to provide Simula’s facilities for program.
Inheritance // A simple class hierarchy. // A class for two-dimensional objects. class TwoDShape { double width; double height; void showDim() { System.out.println("Width.
Esempio Polimorfismo1 // Definition of abstract base class Shape #ifndef SHAPE_H #define SHAPE_H class Shape { public: virtual double area() const { return.
CPA: C++ Polymorphism Copyright © 2007 Mohamed Iqbal Pallipurath Overview of C++ Polymorphism Two main kinds of types in C++: native and user-defined –User-defined.
CS106X – Programming Abstractions in C++ Cynthia Bailey Lee CS2 in C++ Peer Instruction Materials by Cynthia Bailey Lee is licensed under a Creative Commons.
1 Inheritance Concepts n Derive a new class (subclass) from an existing class (base class or superclass). n Inheritance creates a hierarchy of related.
C++ Training Datascope Lawrence D’Antonio Lecture 1 Quiz 1.
. Plab – Tirgul 11 RTTI, Binary files. RTTI – why? Problem: u Up-casting works fine.  Treating sub-class as base class Shape * s = new Circle(); u What.
Stacks  Standard operations: IsEmpty … return true iff stack is empty Top … return top element of stack Push … add an element to the top of the stack.
DERIVED CLASSES AND INHERITANCE Moshe Fresko Bar-Ilan University Object Oriented Programing
Run Time Type Information, Binary files. RTTI – why? Problem: Up-casting works fine. –Treating sub-class as base class Shape * s = new Circle(); What.
Virtual Functions Junaed Sattar November 10, 2008 Lecture 10.
Data Structures Chapter 2 Stacks Andreas Savva. 2 Stacks A stack is a data structure in which all insertions and deletions of entries are made at one.
Computer Science and Software Engineering University of Wisconsin - Platteville 7. Inheritance and Polymorphism Yan Shi CS/SE 2630 Lecture Notes.
Abstract Classes 1. Objectives You will be able to: Say what an abstract class is. Define and use abstract classes. 2.
1 Data Structures - CSCI 102 CS102 C++ Polymorphism Prof Tejada.
CSE 425: Object-Oriented Programming II Implementation of OO Languages Efficient use of instructions and program storage –E.g., a C++ object is stored.
Run-Time Type Identification Jim Fawcett CSE687 – Object Oriented Design Spring 2007.
Object-oriented programming: C++ class A { private: …… // can be accessd by A protected: …… // can be accessed by A and // its derived classes public:
1 Inheritance Inheritance is a relationship among classes wherein one class shares the structure and/or behavior that is defined in one or more other classes.
OOP, Virtual Functions and Inheritance
Structured Programming Good for programming in the small Often doesn't scale up Limitations –Changes at top may affect lower-level algorithms –Code reuse.
Object-Oriented Programming  Object-oriented programming views a program as collection of agents, termed objects. Each object is responsible for specific.
Object Oriented Programming in C++ Chapter 6 Inheritance.
Polymorphism and Virtual Functions. Topics Polymorphism Virtual Functions Pure Virtual Functions Abstract Base Classes Virtual Destructors V-Tables Run.
Chapter 10 Inheritance and Polymorphism
Chapter 9 Questions 1. What are the difference between constructors and member functions? 2. Design and implement a simple class as you want, with constructors.
Nyhoff, ADTs, Data Structures and Problem Solving with C++, Second Edition, © 2005 Pearson Education, Inc. All rights reserved Stacks.
Variables and Data Types.  Variable: Portion of memory for storing a determined value.  Could be numerical, could be character or sequence of characters.
Inheritance Initialization & Destruction of Derived Objects Protected Members Non-public Inheritance Virtual Function Implementation Virtual Destructors.
Inheritance and Composition Reusing the code and functionality Unit - 04.
Friend classes Friend class methods Nested classes Throwing exceptions, try blocks and catch blocks Exception classes Runtime type identification (RTTI)
Overview of C++ Polymorphism
From C to C++. What is C++ C++ is the work of Bjarne Stroustrup of AT&T Bell Labs. C++ is a mostly upward compatible extension of C that provides: A better.
1 Classes classes and objects - from object-oriented programming point of view class declaration class class_name{ data members … methods (member functions)
C++ Namespaces, Exceptions CSci 588: Data Structures, Algorithms and Software Design All material not from online sources copyright © Travis Desell, 2011.
Templates. C++ 2 Outline Function templates  Function template definition  Function template overloading Class templates  Class template definition.
 Inheritance  Protected Members  Non-public Inheritance  Virtual Function Implementation  Virtual Destructors  Abstract Base Classes and Interfaces.
CS32 Discussion Section 1B Week 5 TA: Hao Yu (Cody)
1 Overload assignment = as a member function class point { public:..... point & operator = (const point & a);..... private: double my_x; double my_y;.....
C++ Lesson 1.
C ++ MULTIPLE CHOICE QUESTION
Inheritance.
OOP, Virtual Functions and Inheritance
Inheritance class TwoDShape { private double width;
Data types Data types Basic types
CS 3370 – C++ Object-oriented Programming
Advanced Program Design with C++
7. Inheritance and Polymorphism
Inheritance.
Classes in C++ C++ originally called "C with classes":
תרגול מס' 7 משתנים מיוחסים מבוא ל-C++ קלט/פלט ב-C++
Reserved Words.
null, true, and false are also reserved.
Classes in C++ C++ originally called "C with classes":
Inheritance Virtual Functions, Dynamic Binding, and Polymorphism
CS1201: Programming Language 2
CSC 270 – Survey of Programming Languages
Exceptions 1 CMSC 202.
Polymorphism.
Inheritance Virtual Functions, Dynamic Binding, and Polymorphism
Overview of C++ Polymorphism
CMSC 341 Stacks and Queues 4/17/2019.
CMSC 341 Stacks and Queues 4/17/2019.
CMSC 341 Stacks and Queues 4/29/2019.
Chapter 11 - Templates Outline Introduction Function Templates Overloading Function Templates Class Templates Class.
CMSC 341 Lecture 7.
CS 144 Advanced C++ Programming March 28 Class Meeting
Abstract Data Types Stacks CSCI 240
Presentation transcript:

תרגול מס' 11 הורשה פולימורפיזם

דוגמה תחביר יחס is-a החלפת פונקציות הורשה דוגמה תחביר יחס is-a החלפת פונקציות מבוא לתכנות מערכות - 234122

דוגמה מנשק גרפי מורכב מרכיבים שונים הקרויים Widgets Entry מנשק גרפי מורכב מרכיבים שונים הקרויים Widgets לכל הרכיבים יש תכונות משותפות למשל רוחב, גובה, מיקום לרכיבים מסוימים יש תכונות ייחודיות פעולה המתבצעת לאחר לחיצה על Button קריאת הערך השמור ב-Entry עצם מסוג חלון יצטרך לשמור את כל הרכיבים הנמצאים בו Radio button Check button Button מבוא לתכנות מערכות - 234122

דוגמה אם ננסה לממש מחלקות עבור Widgets ניתקל במספר בעיות: אין דרך נוחה להחזיק את כל ה-Widgets בתוך Window איך היינו פותרים את הבעיה ב-C? מה החסרונות של פתרון זה? class Button { int x, y; int width, height; string text; //... public: int getWidth(); int getHeight(); void onClick(); }; class Entry { int x, y; int width, height; string text; //... public: int getWidth(); int getHeight(); string getValue(); }; class Window { Array<Button> buttons; Array<Entry> entries; // ... for every type // ... }; ב-C ניתן לנסות את הפתרון הבא: ניצור ADT עבור Widget וכל אחד מרכיבים הספציפיים יחזיק שדה ל-Widget את הרשימה של כל ה-Widgets נוכל לשמור בעזרת void* החסרונות של הפתרון יהיו: צריך "לתפור" את הפונקציונליות של Widget אל המנשק של כל אחד מה-ADT שניצור השימוש ב-void* רגיש לבאגים, צריך לדעת איך להוציא כל Widget מהרשימה בצורה בטוחה בלי להתבלבל אפשר לשכלל את הפתרון של C בכל מיני דרכים (Widget שמחזיק שדה פנימי מטיפוס void* עבור ההרחבה) אבל החסרונות עדיין נשארים - כתיבת קוד ידני והמרות בזמן ריצה. מבוא לתכנות מערכות - 234122

הורשה class Widget { int x, y, width, height; string text; // ... public: int getWidth(); int getHeight(); // ... }; ניתן להשתמש בהורשה כדי לציין שמחלקה מסוימת יורשת את התכונות של מחלקת בסיס (מחלקת אב) כלשהי ניצור מחלקה עבור Widget אשר תייצג את התכונות המשותפות לכל ה-Widgets כל Widget יירש את התכונות המשותפות ויוסיף את התכונות הייחודיות לו מונחים שונים לתיאור הורשה - אם B יורשת מ-A אומרים: B inherits A B extends A B is derived from A B is a subclass of A A is a superclass of B A is a base class of B שאלות שצצות: האם יש הורשה שאינה public? כן, אבל היא פחות שימושית ומחוץ לחומר הקורס. האם ניתן לרשת ממספר מחלקות? כן, אך זה יעיל לשימושים מתקדמים יותר וגם מחוץ לחומר הקורס. class Button : public Widget { // ... public: void onClick(); // ... }; class Entry : public Widget { // ... public: string getValue(); // ... }; מבוא לתכנות מערכות - 234122

הורשה - תחביר class A { public: int n; int f() { return n; } }; class B : public A { public: int m; int g() { return m + n; } }; int main() { B b; b.n = 5; b.m = 4; cout << b.f() << endl; cout << b.g() << endl; return 0; }   כדי לציין שמחלקה B יורשת את מחלקה A נוסיף אחרי שם המחלקה B “: public A” לעצם מטיפוס B יהיו את כל השדות והמתודות המוגדרים ב-A בנוסף לאלו המוגדרים בו נהוג לסמן הורשה בתרשימים על ידי חץ היוצא מהמחלקה היורשת אל מחלקת האב, למשל: A B Button Entry Widget מבוא לתכנות מערכות - 234122

בקרת גישה בהורשה class A { int n; protected: int f() { return n; } public: void set(int i) { n = i; } }; class B : public A { public: int g() { return f(); } }; int main() { B b; b.n = 5; // error b.set(5); // o.k. b.f(); // error cout << b.g() << endl; return 0; } מתודות ושדות פרטיים של מחלקת האב אינם נגישים ממחלקות יורשות ניתן להגדיר שדות ומתודות כ-protected, מתודות ושדות אלו יהיו: נגישים ממחלקות יורשות אינם נגישים משאר הקוד בעזרת protected ניתן להגדיר מנשק נפרד לקוד המשתמש במחלקה על ידי הורשה מהמנשק לקוד חיצוני כמו תמיד, נעדיף להסתיר את מימוש המחלקה: נעדיף protected על public נעדיף private על protected מבוא לתכנות מערכות - 234122

בקרת גישה בהורשה A private members protected members public members B class A { // ... };   class B : public A { class C : public B { A private members protected members public members B private members protected members public members User code C private members protected members public members מבוא לתכנות מערכות - 234122

הורשה - is a הורשה מממשת יחס “is a” בין המחלקה היורשת למחלקת האב class A { int n; public: int f() { return n; } void set(int i) { n = i; } }; class B : public A { int g() { return f() + 1; } }; void h(A& a) { a.f(); } int main() { B b; A& a = b; // o.k. A* ptra = new B; // o.k. h(b); return 0; } הורשה מממשת יחס “is a” בין המחלקה היורשת למחלקת האב “a button is a widget” - כפתור הוא רכיב גרפי אם B יורש מ-A נוכל להשתמש בעצם מטיפוס B בכל מקום בו ניתן להשתמש בעצם מטיפוס A B& יוכל לשמש כ-A& כתובת של עצם מסוג B יכולה לשמש ככתובת של עצם מסוג A בעזרת הורשה נוכל להתייחס לעצמים מטיפוסים שונים דרך החלק המשותף להם Array<Widget*> יחיד יאחסן את כל ה-widgets מונחים מקובלים לתיאור היחס is-a: B is an A B is a type of A B is a subtype of A B הוא סוג של A מבוא לתכנות מערכות - 234122

הקומפיילר יוסיף קריאה ל-A() הורשה - בנאים והורסים class A { int n; public: A(int n) : n(n) {} A() : n(0) {} };   class B : public A { int m; B(int n) : A(n), m(n) {} B() : m(1) {} בנאים והורסים אינם עוברים בירושה אתחול ושחרור מחלקת הבסיס מתבצע בדומה לאתחול ושחרור שדות ברשימת האתחול של B נקרא הבנאי של A מחלקת הבסיס מאותחלת לפני השדות אם לא מופיעה קריאה מפורשת לבנאי של A ייקרא A() אם לא מוגדר בנאי A() תתקבל שגיאת קומפילציה B אינו מאתחל שדות של A, אתחולם מתבצע על ידי A לאחר הריסת כל השדות של של B ייקרא ~A() בנאי ההעתקה ואופרטור= הנוצרים על ידי הקומפיילר קוראים להעתקה/השמה של מחלקת האב הקומפיילר יוסיף קריאה ל-A() מבוא לתכנות מערכות - 234122

הורשה - זמני קריאה מה מדפיס הקוד הבא? class X { int n; public: X(int n) : n(n) { cout << "X::X():" << n << endl; } ~X() { cout << "X::~X():" << n << endl; } }; class A { X x1; A(int n) : x1(n) { cout << "A::A()" << endl; } ~A() { cout << "A::~A()" << endl; } }; מה מדפיס הקוד הבא? class B : public A { X x2; public: B(int m, int n) : A(m), x2(n) { cout << "B::B()" << endl; } ~B() { cout << "B::~B()" << endl; };   int main() { B b(1, 2); cout << "=========" << endl; return 0; X::X():1 A::A() X::X() 2 B::B() ========= B::~B() X::~X():2 A::~A() X::~X():1 מבוא לתכנות מערכות - 234122

דוגמה - MultiStack ברצוננו ליצור את המחלקה MultiStack אשר תומכת בכל הפעולות של מחסנית רגילה (push, pop, top) ובפעולה נוספת popk אשר מוציאה את k האיברים האחרונים שהוכנסו למחסנית קיימות שלוש דרכים לפתרון הבעיה: כתיבת MultiStack מחדש שכפול קוד ועבודה מיותרת שימוש ב-Stack כשדה של MultiStack נצטרך "לתפור" ידנית את המתודות MultiStack תירש את Stack קוד אשר עובד עם Stack יוכל לעבוד גם עם MultiStack class Stack { int* data; int size; int nextIndex; public: Stack(int size = 100); Stack(const Stack& stack); ~Stack(); Stack& operator=(const Stack& s); void push(const int& n); void pop(); int& top(); const int& top() const; int getSize() const; class Full {}; class Empty {}; }; מבוא לתכנות מערכות - 234122

MultiStack עלינו לממש ב-MultiStack רק את הבנאי, popk וחריגה חדשה class MultiStack : public Stack { public: MultiStack(int size); void popk(int k); class NotEnoughNumbers {}; }; MultiStack::MultiStack(int size) : Stack(size) {}   void MultiStack::popk(int k) { if (getSize() < k) { throw NotEnoughNumbers(); } for(int i = 0; i < k; ++i ) { pop(); עלינו לממש ב-MultiStack רק את הבנאי, popk וחריגה חדשה האם התשובה משתנה אם השדה nextIndex היה מוגדר כ-protected? השימוש ב-protected יכול לעזור ליורשים אך מוסיף תלויות במימוש בדרך כלל נעדיף להימנע מחשיפת המימוש למחלקות יורשות void MultiStack::popk(int k) { if (nextIndex < k) { throw NotEnoughNumbers(); } nextIndex -= k; } מבוא לתכנות מערכות - 234122

הגדרה מחדש של מתודות class Point { int x, y; public: Point(int x, int y); void print() const; }; void Point::print() const { cout << x << "," << y << endl; } מחלקות יורשות יכולות להגדיר מחדש גם פונקציות קיימות פעולה זו קרויה overriding במקרה זה הפונקציה של מחלקת האב מוסתרת על ידי הפונקציה החדשה כדי לקרוא לפונקציה הישנה מציינים את ה-namespace של מחלקת האב לפני הקריאה לפונקציה קריאה של הפונקציה המוגדרת מחדש לפונקציה אותה היא מסתירה שכיחה מאחר ובד"כ הפונקציה החדשה מוסיפה על ההתנהגות של הפונקציה הקודמת כדי לקרוא לאופרטור שמגידירם מחדש יש להשתמש בתחביר של קריאה לפונקציה רגילה ולא בתחביר האופרטור, למשל A::operator+(...) בד"כ כתיבה מחדש של פונקציות מתבצעת רק עבור פונקציות וירטואליות. העמסת פונקציה המקבלת פרמטרים אחרים מזאת אשר במחלקת האב תסתיר את הפונקציה של מחלקת האב. מומלץ לא לערבב overriding ו-overloading. התוצאות קשות לצפייה ומזמינות באגים. אפשר להגדיר שדות מחדש, אך זה מתכון לבאגים ואין לזה את המשמעות שיש להגדרה מחדש פונקציות (כמו שנראה בנושא הבא) class LabeledPoint : public Point { string label; public: LabeledPoint(string s, int x, int y); void print() const; }; void LabeledPoint::print() const { cout << label << ": "; Point::print(); } void f() { LabeledPoint p("origin", 0, 0); p.print(); // origin: 0,0 } מבוא לתכנות מערכות - 234122

הורשה סיכום מחלקה B יכולה לרשת את התכונות של מחלקה A על ידי שימוש בהורשה אם B יורשת מ-A היא מקבלת את כל השדות והמתודות שלה לא ניתן לגשת מ-B לחלקים פרטיים של A ניתן להגדיר שדות ומתודות כ-protected כדי לאפשר למחלקה יורשת לגשת אליהם אם B יורשת מ-A אז B is an A וניתן להשתמש ב-B& כ-A& וב-B* כ-A* אתחול ושחרור מחלקת הבסיס מתבצע כמו אתחול ושחרור של שדה מומלץ להסתיר את מימוש המחלקה ככל הניתן, גם ממחלקות יורשות ניתן להגדיר מחדש מתודות של מחלקת האב במחלקת הבן מבוא לתכנות מערכות - 234122

פונקציות וירטואליות מחלקות מופשטות (Abstract classes) חריגות והורשה פולימורפיזם פונקציות וירטואליות מחלקות מופשטות (Abstract classes) חריגות והורשה מבוא לתכנות מערכות - 234122

חיה: משקל, גובה, ת. לידה, הדפס סוגי מזון, השמע קול פולימורפיזם פולימורפיזם (רב-צורתיות) מאפשרת יצירת קוד יחיד המתאים לטיפוסים שונים אם B נראה כמו A ומתנהג כמו A אז הוא יוכל להחליף את A ניתן לכתוב קוד פולימורפי עבור טיפוס A אשר יעבוד לכל טיפוס B אשר יורש מ-A נשתמש בהורשה כדי לייצג חיות שונות בעזרת מחלקות כך שנוכל לכתוב קוד פולימורפי עבור כל סוגי החיות חיה: משקל, גובה, ת. לידה, הדפס סוגי מזון, השמע קול פולימורפיזם במדעי המחשב מוזכר בדרך כלל בהקשר של פולימורפיזם של תכנות מונחה עצמים הקרוי גם פולימורפיזם על ידי הורשה או על ידי subtyping. קיימים ארבעה סוגים של פולימורפיזם בקוד, כולם כבר ידועים לנו: על ידי העמסה על ידי המרות על ידי תבניות על ידי הורשה שני הסוגים הראשונים פחות מועילים מאחר והם דורשים התעסקות פרטנית לכל טיפוס. מבוא לתכנות מערכות - 234122

פולימורפיזם ניצור את המחלקה Animal ואת המחלקה Dog אשר תירש ממנה class Animal { int age, weight; public: Animal(int age, int weight); int getAge() const; void makeSound() const { cout << endl; } // no sound by default; }; class Dog: public Animal { Dog(int age, int weight); cout << "vuf vuf" << endl; } }; ניצור את המחלקה Animal ואת המחלקה Dog אשר תירש ממנה מה ידפיס הקוד הבא? בעיה: הקומפיילר בוחר איזו פונקציה תיקרא בזמן הקומפילציה (קישור סטטי) הפונקציה שאנו רוצים שתרוץ תלויה בטיפוס בזמן הריצה Dog* d = new Dog(3,4); Animal* a = d; a->makeSound(); d->makeSound(); מקושרות סטטית - הכתובת אליה קופצים לתחילת ביצוע הפונקציה נקבעת בזמן הקומפילציה המונחים סטטי ודינאמי בהקשר של תכנות מתייחסים לזמן הקומפילציה לעומת זמן הריצה: סטטי - זמן הקומפילציה דינאמי - זמן ריצת התכנית Animal::makeSound Dog::makeSound מבוא לתכנות מערכות - 234122

פונקציות וירטואליות ניתן להכריז על פונקציה כוירטואלית במחלקת האב: class Animal { int age, weight; public: Animal(int age, int weight); int getAge() const; virtual void makeSound() const { cout << endl; } // no sound by default; }; class Dog: public Animal { Dog(int age, int weight); void makeSound() const { cout << "vuf vuf" << endl; } }; ניתן להכריז על פונקציה כוירטואלית במחלקת האב: במקרה זה הקומפיילר ייצור קוד אשר יבחר את הפונקציה המתאימה לטיפוס בזמן הריצה (קישור דינאמי) כעת בקריאה a->makeSound() תתבצע הפונקציה המתאימה אם פונקציה מוכרזת כוירטואלית אז היא וירטואלית בכל המחלקות היורשות הקומפיילר פעיל רק בזמן הקומפילציה, כדי לאפשר קישור דינמי הקומפיילר כותב קוד "מסובך" יותר כך שבזמן הריצה תתבצע קפיצה לפונקציה הנכונה בהתאם לטיפוס הדינמי של המשתנה. מבוא לתכנות מערכות - 234122

אם מתכננים להשתמש בפולימורפיזם חייבים ליצור הורס וירטואלי פונקציות וירטואליות class Dog: public Animal { public: Dog(int age, int weight); void makeSound() const { cout << "vuf vuf" << endl; } }; class Cat: public Animal { Cat(int age, int weight); cout << "miao" << endl; }; class Fish: public Animal { Fish(int age, int weight); }; // the default makeSound is OK void foo() { Animal* animals[3]; animals[0] = new Dog(3,4); animals[1] = new Fish(1,1); animals[2] = new Cat(2,2); for (int i = 0; i < 3; ++i) { animals[i]->makeSound(); delete animals[i]; } vuf vuf miao אם ההורס אינו מוגדר כוירטואלי אז הקומפיילר אמנם ייקרא להורס אוטומטית עם קריאת delete אבל ל-Animal::~Animal(). שגיאה בהרצת ההורס תגרום לקוד לא מוגדר, בפרט היא בטוח תגרום להורס של החיה הספציפית לא להיקרא ולחלק מהקוד לא להתבצע. אם ההורס מוגדר כוירטואלי הקומפיילר ייצור קוד אשר יבחר את ההורס המתאים בזמן הריצה. מה ייקרא כאן? class Animal { int age, weight; public: Animal(int age, int weight); virtual ~Animal() {} // ... }; אם מתכננים להשתמש בפולימורפיזם חייבים ליצור הורס וירטואלי מבוא לתכנות מערכות - 234122

מחלקות אבסטרקטיות במקרים רבים מחלקת האב אינה טיפוס שלם בפני עצמה ניתן להגדיר פונקציה כ-pure virtual על ידי הוספת “= 0” בסוף הכרזתה פונקציה וירטואלית טהורה אינה ממומשת במחלקת האב מחלקה המכילה פונקציה וירטואלית טהורה נקראת מחלקה אבסטרקטית לא ניתן ליצור עצם מטיפוס המחלקה חייבים ליצור עצם מטיפוס היורש ממנה ניתן ליצור מצביעים ורפרנסים למחלקות אבסטקרטיות למעשה שימוש בפונקציה וירטואלית משמעותי רק עבור מצביעים או רפרנסים class Shape { int center_x, center_y; public: Shape(int x, int y) : center_x(x), center_y(y) {} virtual ~Shape() {} virtual double area() const = 0; }; שימו לב להבדל בין הכזרהעל פונקציה (; בסוף שם הפונקציה) לבין מימוש ריק של הפונקציה ( {} בסוף שם הפונקציה) אם תכריזו על ההורס הוירטואלי ותשימו ; לא יימצא לו מימוש בשלב הקישור ותתקבל שגיאה. ברצוננו לקבל הורס ריק ולכן אנחנו מממשים אותו בתוך המחלקה כריק {} - חסר קוד מאחר והוא הוגדר כוירטואלי הקומפיילר ידע לקרוא להורס הנכון, וההורס יהיה וירטואלי לכל המחלקות היורשות. לכן יהיה בטוח לחלוטין לקרוא ל-delete על מצביע ל-Shape. ניתן לתת מימוש גם לפונקציה שהיא pure virtual, כדי לתת איזה מימוש משותף למבקרה שמחלקות הבנים לא יממשו את הפונקציה הנ"ל (מוש אופייני יכלול זריקת חריגה) area היא פונקציה וירטואלית טהורה Shape היא מחלקה אבסטרקטית, לא ניתן ליצור עצם מטיפוס Shape מבוא לתכנות מערכות - 234122

מחלקות אבסטרקטיות class Shape { int center_x, center_y; public: Shape(int x, int y) : center_x(x), center_y(y) {} virtual ~Shape() {} virtual double area() const = 0; }; class Circle : public Shape { int radius; public: Circle(int x, int y, int radius) : Shape(x,y), radius(radius) {} virtual double area() const { return radius*radius*PI; } };   class Square : public Shape { int edge; public: Square(int x, int y, int edge) : Shape(x,y), edge(edge) {} virtual double area() const { return edge*edge; } }; void foo() { Shape* shapes[N]; // an array of squares & circles // initialization ... double totalArea=0; for (int i = 0; i < N; ++i) { totalArea += shapes[i]->area(); } cout << totalArea << endl; }   מבוא לתכנות מערכות - 234122

הוספת Widget חדש אינה דורשת שינויים ב-Window שליחת הודעות לעצמים class Widget { //... virtual void redraw(); };   class Window { Array<Widget*> children; public: void redraw(); void Window::redraw() { for(int i=0;i<children.size();++i) { children[i]->redraw(); } פונקציות וירטואליות מפרידות את ההודעה הנשלחת לעצם מהפעולה שמתבצעת בפועל ההודעה area גורמת לחישוב שונה לכל צורה במקום לשאול עצם מיהו ולהגיד לו מה לעשות בהתאם - פשוט שולחים לו את ההודעה והיא תפורש בהתאם לזהותו קוד אשר משתמש בפולימורפיזם מחליף שימוש ב-if ו-switch בקריאות לפונקציות וירטואליות קור קצר יותר ונקי יותר קל להוסיף מקרים חדשים הוספת Widget חדש אינה דורשת שינויים ב-Window מבוא לתכנות מערכות - 234122

פולימורפיזם והעתקת עצמים פולימורפיזם והעברה by-value (או העתקת עצמים בכלל) אינם משתלבים: עצם המוחזק “by value” (כלומר לא כרפרנס או מצביע) לא יכול להיות בעל טיפוס שונה בזמן הריצה שימוש ב-copy c’tor יוצר עצם מהטיפוס המוגדר לפי שמו לכן העתקת Animal יוצרת Animal חדש בהתבסס על חלק ה-Animal של a לא ניתן ליצור העתק Shape של s כי Shape אבסטרקטית כאשר משתמשים בהורשה עושים זאת עם רפרנסים ומצביעים הדבר נכון גם להעברת והחזרת ערכים void foo(Animal& a, Shape& s) { Animal copy = Animal(a); copy.makeSound(); a.makeSound(); Shape copy2 = Shape(s); // error } מבוא לתכנות מערכות - 234122

what היא פונקציה וירטואלית המוגדרת ב-std::exception חריגות ופולימורפיזם class Stack { // ... class Exception : public std::exception {}; class Full : public Exception {}; class Empty : public Exception {}; }; השימוש בפולימורפיזם מאפשר הגדרת היררכיה של חריגות ניתן לתפוס חריגות לפי מחלקת האב שלהן וכך לאפשר תפיסה של חריגות ספציפיות או חריגות מקבוצה כללית יותר חשוב לתפוס חריגות עם &, למה? הספריה הסטנדרטית מגדירה מספר חריגות שלכולן אב משותף - std::exception מומלץ שחריגות יירשו את exception (בעקיפין או ישירות) void f() { try { Stack s(100); do_stuff(s); } catch (Stack::Full& e) { cerr << "Not enough room"; } catch (Stack::Exception& e) { cerr << "Error with stack"; } catch (std::exception& e) { cerr << e.what() << endl; } תפיסה ללא & היא תפיסה by value - יווצר העתק מהטיפוס המדויק הרשום בעזרת copy c’tor כתוצאה מכך לא יישמר הטיפוס האמיתי של החריגה. שימו לב שאם מעתיקים עצם בעזרת c’tor נוצר עצם מטיפוס המוגדר על ידי ה-c’tor. what היא פונקציה וירטואלית המוגדרת ב-std::exception מבוא לתכנות מערכות - 234122

חריגות ופולימורפיזם class AException {}; class BException : public AException {}; class CException : אם מספר כללי catch מתאימים לתפיסת חריגה ייבחר הכלל אשר מופיע ראשון לכן מתחילים מהמקרה הספציפי ביותר עד לכללי ביותר (לכן שימוש ב-“...” תמיד יופיע אחרון) מה יודפס בכל אחת מהפונקציות? void f() { try { throw CException(); } catch (BException& b) { cout << "B caught"; } catch (CException& c) { cout << "C caught"; } catch (AException& a) { cout << "A caught"; } void g() { try { throw CException(); } catch (BException& b) { cout << "B caught"; } catch (AException& a) { cout << "A caught"; } catch (CException& c) { cout << "C caught"; } void h() { try { throw AException(); } catch (BException& b) { cout << "B caught"; } catch (CException& c) { cout << "C caught"; } catch (AException& a) { cout << "A caught"; } מבוא לתכנות מערכות - 234122

שימוש נכון בהורשה e יכול להיות Circle! כאשר יוצרים מחלקה B היורשת את A חשוב להקפיד: בכל מקום שבו ניתן להשתמש ב-A יהיה ניתן להשתמש ב-B נניח שנרצה להוסיף מחלקה עבור אליפסה האם עיגול הוא סוג של אליפסה? לא, בגלל המתודה setAB עיגול לא יכול לעשות את כל מה שאליפסה עושה המחלקה היורשת צריכה להוסיף על התנהגות מחלקת האב לא להסתיר לא לשנות במקרה שלנו Ellipse ו-Circle צריכות לרשת ישירות את Shape class Ellipse : public Shape { int a,b; public: Ellipse(int x, int y, int a, int b); virtual double area() const; void setAB(int a, int b); };   class Circle : public Ellipse { public: Circle(int x, int y, int radius) : Ellipse(x,y,r,r) {} }; void break_stuff(Ellipse& e) { e.setAB(1,2); } e יכול להיות Circle! מבוא לתכנות מערכות - 234122

פולימורפיזם - סיכום כדי לאפשר התנהגות פולימורפית למספר מחלקות היורשות ממחלקת אב משותפת משתמשים בפונקציות וירטואליות כאשר מריצים פונקציה וירטואלית נבחרת הפונקציה המתאימה בזמן ריצה ניתן להשתמש בפולימורפיזם כדי להעלים if ו-switch מהקוד ולהחליף בקוד פשוט יותר וקל יותר להרחבה ניתן להגדיר מחלקות אבסטרקטיות שחלק מהפונקציות שלהן הן וירטואליות טהורות פונקציות וירטואליות דורשות שימוש במצביעים או רפרנסים נהוג להגדיר את כל החריגות בהיררכיה של הורשות כדי לאפשר תפיסה של קבוצת חריגות כללית בקלות מבוא לתכנות מערכות - 234122

המרות ו-typeid המרות ב-C++ typeid מבוא לתכנות מערכות - 234122

(Type)var או Type(var); המרות ב-++C ניתן ב-C++ להמיר עם תחביר של C או תחביר של בנאי: (Type)var או Type(var); שתי המרות אלו שקולות להמרות אלו חסרונות רבים: המרות מסוג זה הן ערבוב של משמעויות: המרה בין מצביעים עם קשר הורשה ביניהם מוגדרת, אך המרה בין מצביעים שאין קשר ביניהם אינה מוגדרת ניתן בטעות להמיר עצם קבוע (const) ללא קבוע - התוצאה יכולה להיות לא מוגדרת קשה לדעת איזו המרה התבצעה - המרות בין מצביעים מתקמפלות תמיד, אך משמעותן עלולה להשתנות בגלל שינויים ללא התראות מהקומפיילר קשה לזהות את ההמרות בקוד או לחפש אותן לשם כך מוגדרות ב-C++ המרות מיוחדות הפותרות את בעיות אלו מבוא לתכנות מערכות - 234122

המרות ב-C++ קיימים ארבעה סוגי המרות: static_cast: ממירה בין שני עצמים בעזרת בנאי או בין מצביעים/רפרנסים שיש ביניהם קשר של הורשה נכונות ההמרה נבדקת בזמן הקומפילציה בהמרה ממחלקת אב למחלקת בן המשתמש אחראי על הנכונות dynamic_cast: ממירה בין מצביעים/רפרנסים נכונות ההמרה נבדקת בזמן ריצה ומוחזרת שגיאה במקרה של כשלון const_cast: מסירה const מהטיפוס reinterpret_cast: ממירה בין של שני טיפוסים על ידי פירוש מחדש של הביטים משמשת למשחקי ביטים ודיבוג מבוא לתכנות מערכות - 234122

המרות - static_cast void f(int& n, A& base) { double d = static_cast<double>(n); B& derived = static_cast<B&>(base); B* ptr = static_cast<B*>(&base); double* ptr2 = static_cast<double*>(&n); C& unrelated = static_cast<C&>(base); } המרה סטטית מאפשרת המרה בין עצמים בעזרת המרה מוגדרת (ע"י בנאי או אופרטור המרה) במקרה שאין המרה מוגדרת תתקבל שגיאת קומפילציה (בדומה להמרה רגילה) המרה סטטית מאפשרת המרת מצביעים ורפרנסים בין טיפוסים בעלי קשר הורשה אם אין קשר בין הטיפוסים בהמרה סטטית תתקבל שגיאת קומפילציה בהמרה סטטית ממצביע של מחלקת האב למחלקת הבן שאינה נכונה תתקבל התנהגות לא מוגדרת מבוא לתכנות מערכות - 234122

המרות - dynamic_cast המרה דינאמית משמשת להמרה בין מצביעים ורפרנסים תוך כדי בדיקה בזמן ריצה של נכונות ההמרה בהמרה דינאמית של מצביעים יוחזר NULL אם ההמרה אינה אפשרית בהמרה דינאמית של רפרנסים תיזרק std::bad_cast אם ההמרה אינה אפשרית המרה דינאמית עובדת רק על טיפוסים פולימורפיים (בעלי פונקציה וירטואלית אחת לפחות) משתמשים בהמרה דינאמית כדי להמיר בבטחה ממחלקת האב למחלקה יורשת void g(int n, A& base, C& c) { B* ptr = dynamic_cast<B*>(&base); if (ptr == NULL) { cerr << "base is not a B"; } try { B& derived = dynamic_cast<B&>(base); } catch (std::bad_cast& e) { double* ptr2 = dynamic_cast<double*>(&n); A& unrelated = dynamic_cast<A&>(c); } מבוא לתכנות מערכות - 234122

typeid #include <typeinfo> //... class Base { virtual void v() {} }; class Derived : public Base {}; void f() { Base* ptrb = new Base; Base* ptrd = new Derived; cout << typeid(ptrb).name() << endl; cout << typeid(ptrd).name() << endl; cout << typeid(*ptrb).name() << endl; cout << typeid(*ptrd).name() << endl; if (typeid(*ptrd) != typeid(Base)) { cout << "diff"; } return 0;   האופרטור typeid מקבל שם טיפוס או ערך ומחזיר עצם מטיפוס type_info המתאר את שם הטיפוס ניתן להשוות type_info ניתן לקבל מחרוזת המתארת את שם טיפוס (טוב למטרות דיבוג) מומלץ להימנע משימוש ב-typeid שימוש בשאלה "איזה טיפוס אתה?" ובחירה בקוד לפיו מחזירה את החסרונות של קוד ללא פולימורפיזם עבור gcc יודפס: P4Base 4Base 7Derived diff typeid מחושב בזמן קומפילציה עבור חלק מהביטויים (למשל שם טיפוס) ודורש חישוב בזמן ריצה עבור רפרנסים לטיפוסים פולימורפיים. בשביל לעבוד נכון עבור זיהוי רפרנס של מחלקה יורשת על הטיפוס להיות פולימורפי (כמו עבור dynamic_cast) מבוא לתכנות מערכות - 234122

המרות ו-typeid - סיכום המנעו משימוש בהמרות ו-typeid - הם גורמים לקוד שקל להכניס בו באגים וקשה לשנות אותו השתמשו בפונקציות וירטואליות כדי לבצע את מה שדרוש במקום לבדוק איזה טיפוס יש לעצם אם חייבים לבצע המרה השתמשו ב-static_cast אם חייבים להמיר ממחלקת אב לבן השתמשו ב-dynamic_cast המנעו משימוש ב-typeid או מימוש מנגנון דומה בעצמכם המנעו מהמרות בסגנון של C מבוא לתכנות מערכות - 234122

מימוש יחס “is a” מימוש פונקציות וירטואליות העשרה - מימוש הורשה מימוש יחס “is a” מימוש פונקציות וירטואליות מבוא לתכנות מערכות - 234122

העשרה - מימוש הורשה class A { int a; int b; public: int f(); int g(); }; class B : public A { int c; int d; public: int h(); }; כיצד יכול הקומפיילר לממש את ההתנהגות של “B is an A” ? מבנה העצם בזיכרון ייראה כך: תחילה השדות של מחלקת האב אחריהם השדות הנוספים של מחלקת הבן מבנה זה מאפשר לקוד המקבל את כתובת תחילת העצם להתייחס אליו כאל עצם ממחלקת האב כל השדות עבור עצם של מחלקת האב נמצאים במקומם עצם מסוג A A a b מי שמסתכל על תחילת העצם רואה A בכל מקרה מי שמסתכל על תחילת העצם רואה A בכל מקרה עצם מסוג B A 𝚫B a b c d מבוא לתכנות מערכות - 234122

העשרה - פונקציות וירטואליות class A { int a; int b; public: virtual int f(); virtual int g(); }; class B : public A { int c; int d; public: virtual int f(); }; כדי לאפשר קריאה לפונקציה הנכונה בזמן ריצה הקומפיילר משתמש במצביעים לפונקציות לכל מחלקה נשמרת טבלה (הקרויה vtbl) עם מצביעים עבור הפונקציות הוירטואליות המתאימות ביותר לטיפוס התא הראשון של כל עצם יכיל מצביע (הקרוי vptr) אשר יצביע לטבלה המתאימה ביותר לעצם עבור קריאה לפונקציה וירטואלית הקומפיילר ייצור קוד דומה לזה: A::f A::g vtbl for A עצם מסוג A A vptr a b B::f A::g vtbl for B עצם מסוג B A 𝚫B void call_f(A* const this) { this->vptr[0](this); } vptr a b c d להמחשה בלבד מבוא לתכנות מערכות - 234122