ירושה וירטואלית
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 54 מתוך 104.
בעיית היהלום מתרחשת כאשר מחלקה יורשת משתי מחלקות שחולקות מחלקת בסיס משותפת. ללא טיפול מיוחד, למחלקה הנגזרת יש בסופו של דבר שני עותקים של מחלקת הבסיס המשותפת, דבר שגורם לעמימות ולבזבוז זיכרון.
class Animal {
public:
int age;
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {};
Bat b;
b.age = 5; // שגיאה: עמימות - איזה 'age'?המחלקה Bat מכילה שני תתי־אובייקטים נפרדים של Animal: אחד דרך Mammal ואחד דרך Bird. כך נוצר תרשים ירושה בצורת יהלום, שעל שמו נקראת הבעיה.
ירושה וירטואלית פותרת את הבעיה בכך שהיא מבטיחה שקיים רק עותק אחד של מחלקת הבסיס המשותפת. הוסף את מילת המפתח virtual בעת ירושה ממחלקת הבסיס המשותפת:
class Animal {
public:
int age;
Animal(int a = 0) : age(a) {}
};
class Mammal : virtual public Animal {
public:
Mammal(int a = 0) : Animal(a) {}
};
class Bird : virtual public Animal {
public:
Bird(int a = 0) : Animal(a) {}
};
class Bat : public Mammal, public Bird {
public:
Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};
Bat b(5);
b.age = 10; // עובד! קיים רק 'age' אחדשימו לב ש-Bat חייבת לאתחל ישירות את Animal בבנאי שלה. בירושה וירטואלית, המחלקה הנגזרת ביותר אחראית לבניית מחלקת הבסיס הווירטואלית, ללא קשר למחלקות הביניים.
אתגר
קלבואו נבנה מערכת לניהול עובדים שמדגימה כיצד ירושה וירטואלית פותרת את בעיית היהלום. תיצרו היררכיה שבה TeamLead יורשת גם מ־Developer וגם מ־Manager, ששתיהן חולקות מחלקת בסיס משותפת Employee.
תארגנו את הקוד בארבעה קבצים:
Employee.h: הגדירו את מחלקת הבסיס המשותפתEmployeeעם:std::string nameו־int idמוגנים- בנאי שמקבל את שני הערכים ומדפיס:
Employee [<name>] hired with ID <id> - מתודת
getInfo()ציבורית שמדפיסה:Employee: <name> (ID: <id>) - הורס וירטואלי שמדפיס:
Employee [<name>] record closed
Developer.h: הגדירו מחלקתDeveloperשמשתמשת בירושה ציבורית וירטואלית מ־Employee:- משתנה חבר מוגן מסוג
std::string language - בנאי שמקבל שם, מזהה ושפה: מעביר את השם והמזהה ל־
Employee, שומר את השפה ומדפיס:Developer [<name>] specializes in <language> - מתודת
code()ציבורית שמדפיסה:<name> is coding in <language> - הורס שמדפיס:
Developer [<name>] signed off
- משתנה חבר מוגן מסוג
Manager.h: הגדירו מחלקתManagerשמשתמשת בירושה ציבורית וירטואלית מ־Employee:- משתנה חבר מוגן מסוג
int teamSize - בנאי שמקבל שם, מזהה וגודל צוות: מעביר את השם והמזהה ל־
Employee, שומר את גודל הצוות ומדפיס:Manager [<name>] leads a team of <teamSize> - מתודת
manage()ציבורית שמדפיסה:<name> is managing <teamSize> people - הורס שמדפיס:
Manager [<name>] stepped down
- משתנה חבר מוגן מסוג
main.cpp: קראו ארבעה קלטים (כל אחד בשורה נפרדת):- שם (מחרוזת)
- מזהה עובד (מספר שלם)
- שפת תכנות (מחרוזת)
- גודל צוות (מספר שלם)
הגדירו מחלקת
TeamLeadשיורשת ציבורית גם מ־Developerוגם מ־Manager:- בנאי שמקבל את כל ארבעת הפרמטרים וחייב לאתחל ישירות את
Employee(מחלקת הבסיס הווירטואלית), ואחר כך אתDeveloperואתManager - הבנאי צריך להדפיס:
TeamLead [<name>] ready to lead and code! - מתודת
showRole()שקוראת ל־getInfo(), ל־code()ול־manage()לפי הסדר הזה - הורס שמדפיס:
TeamLead [<name>] promoted out
צרו אובייקט
TeamLeadבתוך תחום בלוק, קראו ל־showRole(), ואז תנו לו לצאת מהתחום. אחרי הבלוק, הדפיסו:Organization restructured!
לדוגמה, עבור הקלטים Alice, 101, C++ ו־5:
Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!שימו לב שיש רק קריאה אחת לבנאי של Employee וקריאה אחת להורס של Employee: ירושה וירטואלית מבטיחה שקיים רק עותק אחד של מחלקת הבסיס המשותפת. TeamLead חייבת לאתחל ישירות את Employee, משום שעם ירושה וירטואלית, המחלקה הנגזרת ביותר אחראית לבניית מחלקת הבסיס הווירטואלית.
נסו בעצמכם
#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"
using namespace std;
// TODO: הגדר מחלקת TeamLead שיורשת באופן ציבורי גם מ-Developer וגם מ-Manager
// זכור: בהורשה וירטואלית, TeamLead חייבת לאתחל ישירות את Employee (מחלקת הבסיס הווירטואלית)
class TeamLead : public Developer, public Manager {
public:
// TODO: ממש בנאי שמקבל name, id, language ו-teamSize
// יש לאתחל: תחילה את Employee (מחלקת הבסיס הווירטואלית), אחר כך את Developer, ולבסוף את Manager
// יש להדפיס: TeamLead [<name>] ready to lead and code!
TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
: Employee(name, id),
Developer(name, id, language),
Manager(name, id, teamSize) {
// TODO: הדפס את הודעת הבנאי
}
// TODO: ממש את המתודה showRole()
// יש לקרוא ל-getInfo(), ל-code() ול-manage() בסדר הזה
void showRole() {
// TODO: קרא לשלוש המתודות
}
// TODO: ממש את ההורס
// יש להדפיס: TeamLead [<name>] promoted out
~TeamLead() {
// TODO: הדפס את הודעת ההורס
}
};
int main() {
// קרא את הקלטים
string name;
int id;
string language;
int teamSize;
getline(cin, name);
cin >> id;
cin.ignore();
getline(cin, language);
cin >> teamSize;
// TODO: צור אובייקט TeamLead בתוך תחום של בלוק
// קרא ל־showRole(), ואז אפשר לאובייקט לצאת מתחום הבלוק
{
// TODO: צור TeamLead וקרא ל־showRole()
}
// הדפס את ההודעה הסופית אחרי הבלוק
cout << "Organization restructured!" << endl;
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין