1 / 38

תרגול מס' 11

תרגול מס' 11. הורשה פולימורפיזם. הורשה. דוגמה תחביר יחס is-a החלפת פונקציות. דוגמה. מנשק גרפי מורכב מרכיבים שונים הקרויים Widgets לכל הרכיבים יש תכונות משותפות למשל רוחב, גובה, מיקום לרכיבים מסוימים יש תכונות ייחודיות פעולה המתבצעת לאחר לחיצה על Button קריאת הערך השמור ב- Entry

Download Presentation

תרגול מס' 11

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


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

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

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

  4. דוגמה • אם ננסה לממש מחלקות עבור Widgets ניתקל במספר בעיות: • שכפול קוד בין ה-Widgets השונים • אין דרך נוחה להחזיק את כל ה-Widgets בתוך Window • איך היינו פותרים את הבעיה ב-C? מה החסרונות של פתרון זה? classButton { intx, y; intwidth, height; stringtext; //... public: intgetWidth(); intgetHeight(); voidonClick(); //... }; classEntry { intx, y; intwidth, height; stringtext; //... public: intgetWidth(); intgetHeight(); stringgetValue(); //... }; classWindow { Array<Button> buttons; Array<Entry> entries; // ... for every type // ... }; מבוא לתכנות מערכות - 234122

  5. הורשה • ניתן להשתמש בהורשהכדי לציין שמחלקה מסוימת יורשת את התכונות של מחלקת בסיס (מחלקת אב) כלשהי • ניצור מחלקה עבור Widget אשר תייצג את התכונות המשותפות לכל ה-Widgets • כל Widget יירש את התכונות המשותפות ויוסיף את התכונות הייחודיות לו classWidget { intx, y, width, height; stringtext; // ... public: intgetWidth(); intgetHeight(); // ...}; classButton : publicWidget { // ... public: voidonClick(); // ...}; classEntry : publicWidget { // ... public: stringgetValue(); // ...}; מבוא לתכנות מערכות - 234122

  6. הורשה - תחביר classA { public: intn; intf() { returnn; }};classB : publicA { public: intm; intg() { returnm + n; }};intmain() { Bb; b.n = 5; b.m = 4; cout << b.f() << endl; cout << b.g() << endl; return 0;} • כדי לציין שמחלקה B יורשת את מחלקה Aנוסיף אחרי שם המחלקה B“: public A” • לעצם מטיפוס B יהיו את כל השדות והמתודות המוגדרים ב-A בנוסף לאלו המוגדרים בו • נהוג לסמן הורשה בתרשימים על ידי חץ היוצא מהמחלקה היורשת אל מחלקת האב, למשל: A Widget B Button Entry מבוא לתכנות מערכות - 234122

  7. בקרת גישה בהורשה classA { intn; protected: intf() { returnn; } public: voidset(int i) { n = i; }};classB : publicA { public: intg() { return f(); }};intmain() { Bb; 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

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

  9. הורשה - is a classA { intn; public: intf() { returnn; } voidset(int i) { n = i; } };classB : publicA { public: intg() { returnf() + 1; } };voidh(A& a) { a.f(); }intmain() { Bb; 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 B הוא סוג של A מבוא לתכנות מערכות - 234122

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

  11. הורשה - זמני קריאה classX{ intn; public: X(int n) : n(n) { cout<< "X::X():" << n << endl; } ~X() { cout<< "X::~X():" << n << endl;}};classA{ Xx1; public: A(int n) : x1(n) { cout<< "A::A()" << endl;} ~A() { cout<< "A::~A()" << endl; }}; • מה מדפיס הקוד הבא? classB : publicA { Xx2; public: B(int m, int n) : A(m), x2(n) { cout<< "B::B()" << endl; } ~B() { cout<< "B::~B()" << endl; } }; intmain() { B b(1, 2); cout << "=========" << endl; return 0; } מבוא לתכנות מערכות - 234122

  12. דוגמה - MultiStack • ברצוננו ליצור את המחלקה MultiStackאשר תומכת בכל הפעולות של מחסנית רגילה (push, pop, top) ובפעולה נוספת popkאשר מוציאה את k האיברים האחרונים שהוכנסו למחסנית • קיימות שלוש דרכים לפתרון הבעיה: • כתיבת MultiStack מחדש • שכפול קוד ועבודה מיותרת • שימוש ב-Stack כשדה של MultiStack • נצטרך "לתפור" ידנית את המתודות • MultiStack תירש את Stack • קוד אשר עובד עם Stack יוכל לעבודגם עםMultiStack classStack{int* data; intsize; intnextIndex;public:Stack(int size = 100); Stack(constStack& stack); ~Stack(); Stack& operator=(constStack& s);voidpush(constint& n); voidpop(); int& top(); constint& top()const; intgetSize()const;classFull{};classEmpty{};}; מבוא לתכנות מערכות - 234122

  13. MultiStack classMultiStack : publicStack { public: MultiStack(int size); voidpopk(int k); classNotEnoughNumbers {}; }; MultiStack::MultiStack(int size) : Stack(size) {} voidMultiStack::popk(int k) { if (getSize() < k) { throwNotEnoughNumbers(); } for(inti = 0; i < k; ++i ) { pop(); } } • עלינו לממש ב-MultiStack רק את הבנאי, popk וחריגה חדשה • האם התשובה משתנה אם השדה nextIndexהיה מוגדר כ-protected? • השימוש ב-protected יכול לעזור ליורשים אך מוסיף תלויות במימוש • בדרך כלל נעדיף להימנע מחשיפת המימוש למחלקות יורשות voidMultiStack::popk(int k) { if (nextIndex < k) { throwNotEnoughNumbers();}nextIndex-= k;} מבוא לתכנות מערכות - 234122

  14. הגדרה מחדש של מתודות classPoint{ intx, y; public:Point(int x, int y); voidprint() const;};voidPoint::print() const { cout << x << "," << y << endl;} • מחלקות יורשות יכולות להגדיר מחדש גם פונקציות קיימות • פעולה זו קרויה overriding • במקרה זה הפונקציה של מחלקת האב מוסתרת על ידי הפונקציה החדשה • כדי לקרוא לפונקציה הישנה מציינים את ה-namespace של מחלקת האב לפני הקריאה לפונקציה classLabeledPoint : publicPoint { stringlabel; public:LabeledPoint(string s, int x, inty); voidprint() const;};voidLabeledPoint::print() const { cout << label << ": "; Point::print();} voidf() { LabeledPoint p("origin", 0, 0); p.print(); // origin: 0,0} מבוא לתכנות מערכות - 234122

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

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

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

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

  19. פונקציות וירטואליות classAnimal { intage, weight; public: Animal(int age, int weight); intgetAge() const; virtual voidmakeSound() const { cout << endl; } // no sound by default; };classDog: publicAnimal { public: Dog(int age, int weight); voidmakeSound() const { cout << "vufvuf" << endl; } }; • ניתן להכריז על פונקציה כוירטואלית במחלקת האב: • במקרה זה הקומפיילר ייצור קוד אשר יבחר את הפונקציה המתאימה לטיפוס בזמן הריצה (קישור דינאמי) • כעת בקריאה a->makeSound()תתבצע הפונקציה המתאימה • אם פונקציה מוכרזת כוירטואלית אז היא וירטואלית בכל המחלקות היורשות מבוא לתכנות מערכות - 234122

  20. פונקציות וירטואליות classDog: publicAnimal { public: Dog(int age, int weight); voidmakeSound() const { cout << "vufvuf" << endl; } };classCat: publicAnimal { public: Cat(int age, int weight); voidmakeSound() const { cout << "miao" << endl; } };classFish: publicAnimal { public: Fish(int age, int weight); }; // the default makeSound is OK voidfoo() { 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]; } } vufvuf miao מה ייקרא כאן? classAnimal { intage, weight; public: Animal(int age, int weight); virtual~Animal() {} // ... }; אם מתכננים להשתמש בפולימורפיזם חייביםליצור הורס וירטואלי מבוא לתכנות מערכות - 234122

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

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

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

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

  25. חריגות ופולימורפיזם class Stack {// ...classException : publicstd::exception {}; classFull : publicException {}; classEmpty : publicException {}; }; • השימוש בפולימורפיזםמאפשר הגדרת היררכיהשל חריגות • ניתן לתפוס חריגות לפי מחלקתהאב שלהן וכך לאפשר תפיסהשל חריגות ספציפיות או חריגות מקבוצהכללית יותר • חשוב לתפוס חריגות עם &, למה? • הספריה הסטנדרטית מגדירה מספר חריגותשלכולן אב משותף - std::exception • מומלץ שחריגות יירשו את exception(בעקיפין או ישירות) voidf() { 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; } } what היא פונקציה וירטואלית המוגדרת ב-std::exception מבוא לתכנות מערכות - 234122

  26. חריגות ופולימורפיזם classAException {}; classBException: publicAException {}; classCException: publicAException{}; • אם מספר כללי catch מתאימים לתפיסת חריגה ייבחר הכלל אשר מופיע ראשון • לכן מתחילים מהמקרה הספציפי ביותרעד לכללי ביותר (לכן שימוש ב-“...” תמיד יופיע אחרון) • מה יודפס בכל אחת מהפונקציות? voidh() { try { throwAException(); } catch (BException& b) { cout << "B caught"; } catch (CException& c) { cout << "C caught"; } catch (AException& a) { cout << "A caught"; } } voidf() { try { throwCException(); } catch (BException& b) { cout << "B caught"; } catch (CException& c) { cout << "C caught"; } catch (AException& a) { cout << "A caught"; } } voidg() { try { throwCException(); } catch (BException& b) { cout << "B caught"; } catch (AException& a) { cout << "A caught"; } catch (CException& c) { cout << "C caught"; } } מבוא לתכנות מערכות - 234122

  27. שימוש נכון בהורשה • כאשר יוצרים מחלקה B היורשת את A חשוב להקפיד:בכל מקום שבו ניתן להשתמש ב-A יהיה ניתן להשתמש ב-B • נניח שנרצה להוסיף מחלקה עבוראליפסה • האם עיגול הוא סוג של אליפסה? • לא, בגלל המתודה setAB עיגול לא יכוללעשות את כל מה שאליפסה עושה • המחלקה היורשת צריכה להוסיףעלהתנהגות מחלקת האב • לא להסתיר • לא לשנות • במקרה שלנו Ellipse ו-Circle צריכות לרשתישירות את Shape classEllipse : publicShape {inta,b;public: Ellipse(int x, int y, int a, int b); virtualdoublearea() const; voidsetAB(int a, int b);};classCircle : publicEllipse{public:Circle(int x, int y, int radius) : Ellipse(x,y,r,r) {}}; voidbreak_stuff(Ellipse& e) { e.setAB(1,2);} e יכול להיות Circle! מבוא לתכנות מערכות - 234122

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

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

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

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

  32. המרות - static_cast voidf(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

  33. המרות - dynamic_cast • המרה דינאמית משמשת להמרה בין מצביעים ורפרנסים תוך כדי בדיקה בזמן ריצה של נכונות ההמרה • בהמרה דינאמית של מצביעיםיוחזר NULL אם ההמרה אינה אפשרית • בהמרה דינאמית של רפרנסיםתיזרק std::bad_cast אם ההמרה אינה אפשרית • המרה דינאמית עובדת רק על טיפוסים פולימורפיים (בעלי פונקציה וירטואלית אחת לפחות) • משתמשים בהמרה דינאמית כדי להמיר בבטחה ממחלקת האב למחלקה יורשת voidg(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) { cerr << "base is not a B"; } double* ptr2 = dynamic_cast<double*>(&n); A& unrelated = dynamic_cast<A&>(c);} מבוא לתכנות מערכות - 234122

  34. typeid #include<typeinfo> //... classBase { virtualvoidv() {} }; classDerived : publicBase{};voidf() { 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 • שימוש בשאלה "איזה טיפוס אתה?" ובחירה בקוד לפיו מחזירה את החסרונות של קוד ללא פולימורפיזם מבוא לתכנות מערכות - 234122

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

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

  37. העשרה - מימוש הורשה class A { inta; intb; public: intf(); intg(); }; classB : publicA { intc; intd; public: inth(); }; • כיצד יכול הקומפיילר לממש את ההתנהגות של “B is an A” ? • מבנה העצם בזיכרון ייראה כך: • תחילה השדות של מחלקת האב • אחריהם השדות הנוספים של מחלקת הבן • מבנה זה מאפשר לקוד המקבל את כתובת תחילת העצם להתייחס אליו כאל עצם ממחלקת האב • כל השדות עבור עצם של מחלקת האב נמצאים במקומם עצם מסוג A A a b מי שמסתכל על תחילת העצם רואה A בכל מקרה מי שמסתכל על תחילת העצם רואה A בכל מקרה עצם מסוג B A B a b c d מבוא לתכנות מערכות - 234122

  38. העשרה - פונקציות וירטואליות class A { inta; intb; public: virtualintf(); virtualintg();}; classB : publicA { intc; intd; public: virtualintf(); }; • כדי לאפשר קריאה לפונקציה הנכונה בזמן ריצה הקומפיילר משתמש במצביעים לפונקציות • לכל מחלקה נשמרת טבלה (הקרויה vtbl) עם מצביעים עבור הפונקציות הוירטואליות המתאימות ביותר לטיפוס • התא הראשון של כל עצם יכיל מצביע (הקרוי vptr) אשר יצביע לטבלה המתאימה ביותר לעצם • עבור קריאה לפונקציה וירטואלית הקומפיילר ייצור קוד דומה לזה: vtbl for B B::f vtbl for A עצם מסוג A A::g A::f A A::g vptr a b עצם מסוג B A B voidcall_f(A* constthis) { this->vptr[0](this); } vptr a b c d להמחשה בלבד מבוא לתכנות מערכות - 234122

More Related