مشكلة يعرفها الجميع: "لماذا لا يعمل الكود الخاص بي؟"
\n\nهل سبق لك أن كتبت فئة مشتقة، وأعدت تعريف طريقة فيها، ثم اكتشفت بدهشة أن الوظيفة الأساسية للفئة الأصلية قد اختفت في مكان ما؟ مألوف؟ هذه الحالة هي كلاسيكية من نوعها في عالم C++. السؤال "How to call a parent class function from derived class function" على Stack Overflow حصد آلاف المشاهدات ومئات المناقشات. وليس من المستغرب: بدون فهم هذه الآلية، ستستمر في الوقوع في نفس الأخطاء مرارًا وتكرارًا.
\n\nاليوم سنشرح هذا الموضوع مرة واحدة وإلى الأبد. لن تتعلم فقط بناء جملة استدعاء دالة الفئة الأصلية، بل ستفهم أيضًا متى يكون ذلك ضروريًا حقًا، ومتى يكون ضارًا. هيا بنا!
\n\nالأساسيات: الوراثة وإعادة تعريف الطرق
\n\nقبل أن نتحدث عن استدعاء دوال الفئة الأصلية، دعنا نتذكر كيف تعمل الوراثة في C++. تخيل أن لديك فئة أساسية Animal بطريقة speak(). تقوم بإنشاء فئة Dog ترث من Animal، وتعيد تعريف speak() لكي ينبح الكلب بدلاً من إصدار صوت عام.
#include <iostream>\nusing namespace std;\n\nclass Animal {\npublic:\n void speak() {\n cout << "Animal speaks!" << endl;\n }\n};\n\nclass Dog : public Animal {\npublic:\n void speak() {\n cout << "Dog barks!" << endl;\n }\n};\n\nint main() {\n Dog myDog;\n myDog.speak(); // الإخراج: Dog barks!\n return 0;\n}\n\nكل شيء يعمل. ولكن ماذا لو أردت أن تقوم Dog::speak() أولاً بتنفيذ إجراء الفئة الأصلية، ثم تضيف إجراءها الخاص؟ على سبيل المثال: "الحيوان يصدر صوتًا، والكلب ينبح". هنا يبدأ الجزء المثير للاهتمام.
لماذا لا يمكن استدعاء الطريقة الأصلية ببساطة؟
\n\nعند إعادة تعريف طريقة في الفئة المشتقة، يعتبر C++ أنك قد استبدلت السلوك بالكامل. تصبح الطريقة الأصلية "غير مرئية" داخل سياق الفئة المشتقة. إذا كتبت ببساطة speak() داخل Dog::speak()، فسيستدعي المترجم نفسه - مما يؤدي إلى استدعاء متكرر لا نهائي.
الحل: عامل تحديد نطاق الرؤية (::)
\n\nلتوجيه المترجم بوضوح: "مرحبًا، أريد استدعاء الطريقة من الفئة الأصلية بالتحديد!"، يتم استخدام العامل ::. بناء الجملة بسيط: اسمالفئةالأصلية::اسمالطريقة().
class Dog : public Animal {\npublic:\n void speak() {\n Animal::speak(); // استدعاء الطريقة الأصلية\n cout << "Dog barks!" << endl;\n }\n};\n\nint main() {\n Dog myDog;\n myDog.speak();\n // الإخراج:\n // Animal speaks!\n // Dog barks!\n return 0;\n}\n\nلاحظ: نحن لا ننشئ كائنًا جديدًا من الفئة الأصلية. نحن فقط نقول: "خذ تنفيذ speak() من الفئة Animal ونفذه في سياق الكائن الحالي Dog". هذه آلية قوية تسمح ببناء سلاسل من الاستدعاءات.
متى يكون ذلك ضروريًا حقًا؟ 3 سيناريوهات واقعية
\n\n1. توسيع الوظائف (الحالة الأكثر شيوعًا)
\n\nتريد إضافة سلوك جديد دون فقدان السلوك القديم. المثال الكلاسيكي هو المنشئات والمدمرات. نعم، يتم استدعاؤها تلقائيًا، ولكن إذا كنت تعيد تعريف طريقة تهيئة ما، فإن استدعاء الفئة الأصلية إلزامي.
\n\nclass Vehicle {\npublic:\n void start() {\n cout << "Engine started. " << endl;\n }\n};\n\nclass Car : public Vehicle {\npublic:\n void start() {\n Vehicle::start(); // أولاً نقوم بتشغيل المحرك\n cout << "Car is ready to drive!" << endl;\n }\n};\n\n2. العمل مع الدوال الافتراضية وتعدد الأشكال
\n\nإذا تم تعريف الطريقة الأساسية كـ virtual، فقد يكون استدعاء النسخة الأصلية مفيدًا لتنفيذ "السلوك الأساسي الافتراضي"