Menu
Coddy logo textTech

ירושה וירטואלית

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 בבנאי שלה. בירושה וירטואלית, המחלקה הנגזרת ביותר אחראית לבניית מחלקת הבסיס הווירטואלית, ללא קשר למחלקות הביניים.

challenge icon

אתגר

קל

בואו נבנה מערכת לניהול עובדים שמדגימה כיצד ירושה וירטואלית פותרת את בעיית היהלום. תיצרו היררכיה שבה 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: קראו ארבעה קלטים (כל אחד בשורה נפרדת):
    1. שם (מחרוזת)
    2. מזהה עובד (מספר שלם)
    3. שפת תכנות (מחרוזת)
    4. גודל צוות (מספר שלם)

    הגדירו מחלקת 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;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין