Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

الوراثة الافتراضية

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 54 من 104.

تحدث مشكلة الماس (diamond problem) عندما ترث فئة من فئتين تشتركان في فئة أساسية واحدة. وبدون معالجة خاصة، تنتهي الفئة المشتقة بوجود نسختين من الفئة الأساسية المشتركة، مما يسبب غموضاً وهدراً في الذاكرة.

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 بشكل مباشر في المنشئ الخاص به. مع الوراثة الافتراضية (virtual inheritance)، تكون الفئة الأكثر اشتقاقاً هي المسؤولة عن بناء القاعدة الافتراضية، بغض النظر عن الفئات الوسيطة.

challenge icon

التحدي

سهل

لنقم ببناء نظام لإدارة الموظفين يوضح كيف يحل التوريث الافتراضي (virtual inheritance) مشكلة الألماسة (diamond problem). ستقوم بإنشاء تسلسل هرمي حيث يرث TeamLead من كل من Developer و Manager، واللذان يشتركان في فئة أساسية عامة هي Employee.

ستقوم بتنظيم الكود الخاص بك عبر أربعة ملفات:

  • Employee.h: قم بتعريف الفئة الأساسية العامة Employee مع:
    • عضوين محميين (protected) هما std::string name و int id
    • منشئ (constructor) يأخذ القيمتين ويطبع: Employee [<name>] hired with ID <id>
    • دالة عامة getInfo() تطبع: Employee: <name> (ID: <id>)
    • مدمر افتراضي (virtual destructor) يطبع: Employee [<name>] record closed
  • Developer.h: قم بتعريف فئة Developer تستخدم التوريث العام الافتراضي (virtual public inheritance) من Employee:
    • عضو محمي std::string language
    • منشئ يأخذ الاسم، والمعرف، واللغة — يمرر الاسم والمعرف إلى Employee، ويخزن اللغة، ويطبع: Developer [<name>] specializes in <language>
    • دالة عامة code() تطبع: <name> is coding in <language>
    • مدمر يطبع: Developer [<name>] signed off
  • Manager.h: قم بتعريف فئة Manager تستخدم التوريث العام الافتراضي (virtual public inheritance) من Employee:
    • عضو محمي int teamSize
    • منشئ يأخذ الاسم، والمعرف، وحجم الفريق — يمرر الاسم والمعرف إلى Employee، ويخزن حجم الفريق، ويطبع: Manager [<name>] leads a team of <teamSize>
    • دالة عامة manage() تطبع: <name> is managing <teamSize> people
    • مدمر يطبع: Manager [<name>] stepped down
  • main.cpp: اقرأ أربعة مدخلات (كل منها في سطر منفصل):
    1. الاسم (string)
    2. معرف الموظف (integer)
    3. لغة البرمجة (string)
    4. حجم الفريق (integer)

    قم بتعريف فئة TeamLead ترث علنًا من كل من Developer و Manager:

    • منشئ يأخذ جميع المعاملات الأربعة ويجب أن يقوم بتهيئة Employee (القاعدة الافتراضية) مباشرة، ثم Developer و Manager
    • يجب أن يطبع المنشئ: TeamLead [<name>] ready to lead and code!
    • دالة showRole() تستدعي getInfo()، و code()، و manage() بهذا الترتيب
    • مدمر يطبع: TeamLead [<name>] promoted out

    قم بإنشاء كائن TeamLead داخل نطاق محدد (block scope)، واستدعِ 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 مباشرة لأنه مع التوريث الافتراضي، تكون الفئة الأكثر اشتقاقًا (most derived class) هي المسؤولة عن بناء القاعدة الافتراضية.

ورقة مرجعية

تحدث مشكلة الألماسة (diamond problem) عندما ترث فئة (class) من فئتين تشتركان في فئة أساسية واحدة، مما يؤدي إلى وجود نسختين من الفئة الأساسية ويسبب غموضاً.

class Animal {
public:
    int age;
};

class Mammal : public Animal {};
class Bird : public Animal {};

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Error: ambiguous - which 'age'?

تحل الوراثة الافتراضية (Virtual inheritance) مشكلة الألماسة من خلال ضمان وجود نسخة واحدة فقط من الفئة الأساسية المشتركة. استخدم الكلمة المفتاحية 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;  // Works! Only one 'age' exists

مع الوراثة الافتراضية، يجب على الفئة الأكثر اشتقاقاً (most derived class) تهيئة الفئة الأساسية الافتراضية مباشرة في منشئها (constructor)، بغض النظر عن الفئات الوسيطة.

جرّب بنفسك

#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"

using namespace std;

// TODO: قم بتعريف فئة TeamLead التي ترث علنًا من كل من Developer و Manager
// تذكر: مع الوراثة الافتراضية (virtual inheritance)، يجب على TeamLead تهيئة Employee (القاعدة الافتراضية) بشكل مباشر
class TeamLead : public Developer, public Manager {
public:
    // TODO: قم بتنفيذ المنشئ (constructor) الذي يأخذ 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: قم بتنفيذ المدمر (destructor)
    // يجب أن يطبع: 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 داخل نطاق كتلة (block scope)
    // استدعِ showRole()، ثم اتركه يخرج عن النطاق
    {
        // TODO: قم بإنشاء TeamLead واستدعِ showRole()
    }

    // اطبع الرسالة النهائية بعد الكتلة البرمجية
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming