«من مُنشئ إلى آخر»: السؤال الأبدي لمطور C++
\n\nتخيل موقفًا: تكتب كلاسًا بعدة مُنشئات. واحد يستقبل ثلاث معاملات، وآخر اثنين، وثالث واحد. وتتكرر في جميعها نفس منطق التهيئة. تشعر برغبة في كتابة شيء مثل MyClass() : MyClass(42, "default") {} — وتنسى أمر التكرار. لكن مُجمّع C++ (حتى لحظة معينة) ينظر إلى هذا بشكل غير مباشر. هل يمكن أساسًا استدعاء مُنشئ من مُنشئ آخر؟ وإذا كان الجواب نعم — فكيف؟
هذا السؤال هو من أكثر الأسئلة شيوعًا على Stack Overflow (عشرات الآلاف من المشاهدات). ليس مستغربًا: موضوع سلاسل المُنشئات (constructor chaining) هو حجر عثرة حقيقي للمبتدئين وحتى المطورين ذوي الخبرة الذين ينتقلون إلى C++ من Java أو Python. في هذه المقالة، سنحلل ثلاثة أساليب لحل المشكلة، من «الحلول المؤقتة» إلى الحلول الأنيقة في C++11، وسنكتشف أي منها يستحق الاستخدام في الكود الحقيقي.
\n\nلماذا لا يمكن ببساطة استدعاء مُنشئ من مُنشئ؟
\n\nالنهج الساذج يبدو هكذا:
\n\nclass MyClass {\npublic:\n MyClass() {\n // كود ما\n }\n MyClass(int x) {\n MyClass(); // خطأ: لا يمكن فعل هذا!\n }\n};\n\nفي C++، المُنشئ ليس دالة عادية. فهو يُهيئ الكائن مرة واحدة. إذا حاولت استدعاء مُنشئ آخر بشكل صريح داخل الجسم، سيعتبره المُجمّع إنشاء كائن مؤقت — ولن يتم تهيئة أي شيء في المثيل الحالي. النتيجة: إما خطأ في التجميع، أو (ما هو أسوأ) سلوك غير محدد.
\n\nقبل معيار C++11، كانت الطريقة القانونية الوحيدة «لمشاركة» منطق التهيئة هي استدعاء دالة خاصة init(). لكن هذا لم يكن تفويضًا حقيقيًا: أعضاء الكلاس كانوا يُهيؤون أولاً بواسطة المُنشئ الافتراضي، ثم يُعاد كتابتهم. غير فعال ومحفوف بالأخطاء.
النهج رقم 1: المُنشئات المفوضة (C++11 وما بعده)
\n\nبدءًا من C++11، ظهرت في اللغة ميزة طال انتظارها — المُنشئات المفوضة (delegating constructors). الآن يمكن استدعاء مُنشئ من مُنشئ آخر مباشرة في قائمة التهيئة:
\n\nclass MyClass {\n int value;\n std::string name;\npublic:\n // المُنشئ الأساسي\n MyClass(int v, const std::string& n) : value(v), name(n) {\n std::cout << "تهيئة كاملة: " << value << ", " << name << "\\n";\n }\n // المُنشئ المفوض\n MyClass(int v) : MyClass(v, "default") {}\n // مُنشئ مفوض آخر\n MyClass() : MyClass(0, "default") {}\n};\n\nكيف يعمل هذا؟ المُنشئ MyClass(int v) يُسلم التحكم إلى المُنشئ MyClass(int, string). هذا الأخير ينفذ كل التهيئة، وبعدها فقط يعود التحكم مرة أخرى. قاعدة مهمة: في قائمة التهيئة لا يمكن خلط استدعاء مُنشئ آخر مع تهيئة مباشرة للأعضاء. إما تفويض، أو تهيئة خاصة — واحد من الاثنين.
الإيجابيات والسلبيات
\n- \n
- ✅ الأناقة: يصبح الكود نظيفًا، بدون تكرار. \n
- ✅ الأداء: يُهيأ الكائن مرة واحدة، دون عمليات زائدة. \n
- ⚠️ القيد: التفويض يعمل فقط في «عمق» واحد — لا يمكن أن تكون السلسلة متكررة (ستتسبب في تجاوز سعة المكدس). \n
- ⚠️ التوافق: يتطلب C++11 أو أحدث. إذا كنت عالقًا على مُجمّع قديم — هذا الخيار ليس لك. \n
النهج رقم 2: دالة init() الخاصة
\n\nالطريقة الكلاسيكية «ما قبل C++11» — نقل المنطق المشترك إلى دالة خاصة. تبدو هكذا:
\n\nclass MyClass {\n int value;\n std::string name;\n void init(int v, const std::string& n) {\n value = v;\n name = n;\n std::cout << "تم استدعاء init\\n";\n