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

الوراثة في C++: شرح الأصناف الأساسية والمشتقة

تعرّف على كيف تتيح الوراثة في C++ لصنف مشتق إعادة استخدام صنف أساسي وتوسيعه: الصياغة، الوراثة العامة مقابل الخاصة، ترتيب المُنشئات والمُدمّرات، والمزالق مثل تقطيع الكائنات (slicing).

تحتوي هذه الصفحة على محررات قابلة للتشغيل - حرّر، شغّل، وشاهد النتيجة فوراً.

إعادة استخدام صنف عبر توسيعه

لقد بنيتَ أصنافًا باستخدام المُنشئات ونظّفتها باستخدام المُدمّرات. الوراثة هي الخطوة التالية: بدلًا من نسخ أعضاء صنف إلى آخر، تُعلن أن صنفًا جديدًا هو نسخة متخصصة من صنف موجود، وتدعه يرث كل شيء تلقائيًا.

الصنف الموجود هو الصنف الأساسي (أو الأب)؛ والجديد هو الصنف المشتق (أو الابن). يبدأ الصنف المشتق بكل بيانات الصنف الأساسي وسلوكه، ثم يضيف أو يغيّر ما يجعله مختلفًا. هذه هي الأداة الأساسية في C++ لعلاقة "هو نوع من": فـ Dog هو Animal، و SavingsAccount هو BankAccount.

الصياغة الأساسية: class Derived : public Base

ترث بأن تُتبع اسم الصنف المشتق بنقطتين رأسيتين ومُحدِّد وصول واسم الصنف الأساسي. أكثر الصيغ شيوعًا هي الوراثة public.

لا يُعلِن Dog عن name ولا عن eat() على الإطلاق، ومع ذلك يعمل كلاهما على كائن Dog لأنهما وُرِثا من Animal. والصنف المشتق حرٌّ في إضافة أعضاء مثل bark() لا يعرف عنها الصنف الأساسي شيئًا.

protected: أعضاء للأبناء فقط

العضو private في الصنف الأساسي غير متاح داخل الصنف المشتق - فالوراثة لا تكسر التغليف. وعندما تريد إخفاء تفاصيل الصنف الأساسي عن العالم الخارجي مع إتاحتها للأصناف الفرعية، استخدم مُحدِّد الوصول protected.

تخيّل المستويات الثلاثة كحلقات متحدة المركز: private تعني "هذا الصنف فقط"، و protected تعني "هذا الصنف وذرّيّته"، و public تعني "الجميع". راجع مُحدِّدات الوصول للصورة الكاملة.

ترتيب المُنشئات والمُدمّرات

يحتوي الكائن المشتق على كائن جزئي أساسي، ويجب أن يكون ذلك الجزء الأساسي حيًّا قبل أن يُبنى الجزء المشتق. لذلك يجري البناء بدءًا من الأساسي، ويجري الهدم بدءًا من المشتق (بالترتيب المعاكس تمامًا). وإذا احتاج الصنف الأساسي إلى وسائط للمُنشئ، فإنك تمرّرها عبر قائمة تهيئة الأعضاء.

يجعل الخرج الترتيب ملموسًا:

Animal ctor: Rex   // الأساسي يُبنى أولًا
Dog ctor           // ثم الجزء المشتق
Dog dtor           // يُهدَم بالترتيب المعاكس...
Animal dtor: Rex   // ...الأساسي أخيرًا

إذا نسيتَ : Animal(n) ولم يكن للصنف الأساسي مُنشئ افتراضي، فلن تُترجَم الشيفرة - إذ لا تعرف C++ كيف تبني الجزء الأساسي. والصنف الأساسي الذي تنوي الوراثة منه ينبغي في معظم الأحيان أن يُعلِن مُدمّرًا (وغالبًا مُدمّرًا افتراضيًا (virtual)، كما تُبيّن الصفحة التالية).

التجاوز (Overriding): إعادة تعريف دالة من الصنف الأساسي

يستطيع الصنف المشتق استبدال دالة موروثة بأن يُعلِن دالة بالبصمة نفسها. ولا يزال بإمكانك الوصول إلى الأصلية عبر Base::method().

هذا مجرد إخفاء للاسم (name hiding)، وليس تعدد أشكال: فأيّ describe() تُنفَّذ يُحسَم وقت الترجمة بحسب النوع الساكن للمتغير. وهذا قيد جوهري - إذا استدعيتَ عبر Shape& أو Shape* يشير في حقيقته إلى Circle، فستحصل مع ذلك على Shape::describe(). ويتطلب إصلاح ذلك الكلمة المفتاحية virtual، وهي موضوع الصفحة التالية.

احذر من تقطيع الكائنات (slicing)

بما أن مرجعًا أو مؤشرًا للصنف الأساسي يمكنه أن يشير إلى كائن مشتق، فمن المغري نسخ كائن مشتق إلى متغير من النوع الأساسي. لا تفعل ذلك - فالجزء المشتق يُقتطع.

إن a هو Animal حقيقي، وليس Dog يرتدي بطاقة الصنف الأساسي، ولذلك فإن breed ببساطة غير موجود فيه. وللتعامل مع الكائنات المشتقة على نحو متعدد الأشكال، يجب أن تستخدم مرجعًا للصنف الأساسي (Animal&) أو مؤشرًا (Animal*)، لا قيمةً من النوع الأساسي أبدًا. والتقطيع صامت - يُترجَم بنظافة ويُسقِط البيانات بهدوء فحسب - مما يجعله من أسهل عيوب الوراثة وصولًا إلى مرحلة الإنتاج.

أخطاء شائعة ينبغي تجنّبها

  • توقُّع أن تكون أعضاء private في الصنف الأساسي متاحةً في الابن. ليست كذلك. استخدم protected للبيانات التي يحتاجها الصنف المشتق فعلًا بشكل مشروع، وأبقِ الحالة الداخلية الحقيقية private.
  • نسيان تمرير وسائط مُنشئ الصنف الأساسي. إذا لم يكن للصنف الأساسي مُنشئ افتراضي، فيجب أن تستدعيه صراحةً في قائمة تهيئة مُنشئ الصنف المشتق (: Base(args)).
  • تقطيع كائن مشتق إلى قيمة من النوع الأساسي. نسخ Dog إلى Animal يُسقِط كل ما هو خاص بـ Dog. مرِّر وخزِّن مراجع أو مؤشرات للصنف الأساسي بدلًا من ذلك.
  • الإفراط في استخدام الوراثة لإعادة استخدام الشيفرة. الوراثة تُمثّل "هو نوع من". وإذا كانت العلاقة في الحقيقة "يملك" (فـ Car يملك Engine)، ففضِّل التركيب (composition) - أي كائنًا عضوًا - على الوراثة.

التالي: الدوال الافتراضية

التجاوز الذي رأيتَه للتو حُسِم وقت الترجمة، ولذلك تجاهل الاستدعاء عبر مؤشر أساسي النسخةَ المشتقة. الصفحة التالية، الدوال الافتراضية، تُقدّم الكلمة المفتاحية virtual و override - الآلية التي تجعل النوعَ وقت التشغيل هو من يقرّر أيّ دالة تُنفَّذ، فاتحةً الباب أمام تعدد الأشكال الحقيقي، ومُفسِّرةً لماذا تحتاج الأصناف الأساسية إلى مُدمّرات افتراضية.

الأسئلة الشائعة

ما هي الوراثة في C++؟

تتيح لك الوراثة تعريف صنف جديد (الصنف المشتق) اعتمادًا على صنف موجود (الصنف الأساسي). يحصل الصنف المشتق تلقائيًا على أعضاء بيانات الصنف الأساسي ودواله العضوية، ويمكنه إضافة أعضاء جديدة أو استبدال سلوك قائم. وهي تُمثّل علاقة "هو نوع من" (is-a) - فـ Dog هو Animal - وتُعدّ الوسيلة الأساسية التي تعيد بها C++ استخدام الشيفرة وتوسيعها عبر تسلسلات الأصناف.

ما الفرق بين الوراثة العامة والخاصة في C++؟

مع الوراثة public (‏class Dog : public Animal)، تبقى الواجهة العامة للصنف الأساسي عامةً في الصنف المشتق، لذا فإن Dog هو Animal ويمكن استخدامه في أي موضع يُتوقَّع فيه Animal. أما مع الوراثة private، فتصبح الأعضاء الموروثة خاصةً - يعيد الصنف المشتق استخدام تنفيذ الصنف الأساسي لكنه ليس بديلًا قابلًا للإحلال محله. الوراثة العامة هي الحالة الشائعة إلى حدٍّ بعيد؛ ولا تلجأ إلى الخاصة إلا لإعادة الاستخدام من نوع "مُنفَّذ بدلالة".

بأي ترتيب تُنفَّذ المُنشئات والمُدمّرات مع الوراثة في C++؟

تُنفَّذ المُنشئات بدءًا من الأساسي: يُبنى الصنف الأساسي بالكامل قبل تنفيذ متن مُنشئ الصنف المشتق. وتُنفَّذ المُدمّرات بالترتيب المعاكس تمامًا - المشتق أولًا ثم الأساسي. ويضمن هذا أنه أثناء بناء كائن مشتق أو هدمه، يكون كل جزء يعتمد عليه موجودًا بالفعل (أو لا يزال موجودًا).

Coddy programming languages illustration

تعلّم البرمجة مع Coddy

ابدأ الآن