الوراثة في C++: كيفية استدعاء مُنشئ الفئة الأساسية بشكل صحيح دون كسر الكود

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: لماذا تعتبر مُنشئات الفئات الأم حقل ألغام للمبتدئين



كل من بدأ في تعلم الوراثة في C++، عاجلاً أم آجلاً، واجه موقفًا: تقوم بإنشاء فئة فرعية، والمُصرّف (compiler) يشتكي، البرنامج لا يُجمّع، وأنت لا تفهم لماذا؟ غالبًا ما يكمن جذر المشكلة في استدعاء مُنشئ الفئة الأساسية بشكل غير صحيح. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وليس من دون سبب: حتى المطورون ذوو الخبرة ينسون أحيانًا القواعد الأساسية.



في هذه المقالة، سنحلل قواعد استدعاء مُنشئ الفئة الأساسية في C++، وسنستعرض أهم 5 أخطاء شائعة ونوضح كيفية تجنبها. إذا كنت تريد كتابة كود موثوق بدون مفاجآت، تابع القراءة حتى النهاية.



شرح موجز: كيف تعمل المُنشئات عند الوراثة



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



يتم الاستدعاء الصريح عبر قائمة التهيئة:



class Base {public:    Base(int x) { /* ... */ }};

class Derived : public Base {public: // صحيح: استدعاء صريح للمُنشئ Base(int) Derived() : Base(42) { /* ... */ }};


يبدو الأمر بسيطًا؟ لكن هنا يرتكب المبتدئون 90% من الأخطاء. دعنا نستعرضها بالترتيب.



أهم 5 أخطاء عند استدعاء مُنشئ الفئة الأساسية



الخطأ رقم 1: نسيان استدعاء مُنشئ الفئة الأم ذي المعاملات



المشكلة الأكثر شيوعًا. إذا لم يكن للفئة الأساسية مُنشئ افتراضي، ولم تستدعِه صراحةً، سيُصدر المُصرّف خطأ.



class Base {public:    Base(int x) { /* تهيئة */ }};

class Derived : public Base {public: // خطأ: المُصرّف يحاول استدعاء Base() غير الموجود Derived() { }};


كيفية الإصلاح:



class Derived : public Base {public:    // صحيح: استدعاء صريح لـ Base(int)    Derived() : Base(0) { }    // أو تمرير المعامل من الخارج:    Derived(int x) : Base(x) { }};


الخطأ رقم 2: ترتيب التهيئة غير الصحيح



يعتقد الكثيرون أن ترتيب استدعاء المُنشئات في قائمة التهيئة يحدد ترتيب التنفيذ. هذا غير صحيح. يتم دائمًا تهيئة الفئة الأساسية أولاً، بغض النظر عن مكان كتابة استدعائها.



class Derived : public Base {private:    int value;public:    // يبدو أن value سيُهيّأ أولاً؟ لا!    Derived() : value(10), Base(5) { }    // في الواقع: أولاً Base(5)، ثم value(10)};


قد يؤدي هذا إلى أخطاء منطقية إذا كان تهيئة value تعتمد على حالة الفئة الأساسية.



الخطأ رقم 3: استدعاء مُنشئ الفئة الأم داخل جسم المُنشئ



يحاول بعض المبتدئين استدعاء مُنشئ الفئة الأساسية داخل الأقواس المتعرجة، كأنه دالة عادية. هذا لن يعمل — يمكن استدعاء المُنشئ فقط في قائمة التهيئة.



class Derived : public Base {public:    Derived() {        // خطأ: لا يتم استدعاء المُنشئ بهذه الطريقة        Base(10); // فقط ينشئ كائنًا مؤقتًا ويحذفه    }};


الخطأ رقم 4: الخلط مع مُنشئات النسخ



عند تنفيذ مُنشئ النسخ في الفئة المشتقة، يجب استدعاء مُنشئ النسخ للفئة الأساسية صراحةً. وإلا، سيتم استدعاء المُنشئ الافتراضي، ولن تُنسخ بيانات الفئة الأساسية.



class Derived : public Base {public:    // خطأ: سيتم نسخ Base بشكل غير صحيح    Derived(const Derived& other) { /* ... */ }

// صحيح: Derived(const Derived& other) : Base(other) { /* ... */ }};

المدونات

توصيات الكتب