ما هو التأمل والاستبطان في C++ ولماذا نحتاجهما؟
\n\nتخيل أنك تكتب محرك ألعاب معقدًا أو إطار عمل لمعالجة البيانات. في مرحلة ما، تحتاج إلى فهم: "ما هذا الكائن بالضبط؟ ما هي دواله؟ هل لديه حقل name؟"
في لغات مثل Python أو Java، يتم ذلك بسطر واحد - حيث يوجد تأمل كامل (قدرة البرنامج على دراسة وتعديل هيكله أثناء وقت التنفيذ). أما في C++ فالأمر أكثر تعقيدًا. C++ هي لغة ذات كتابة ثابتة قوية، ولا يوجد تأمل "خارج الصندوق" تقريبًا. ولكن يوجد استبطان - قدرة محدودة على الحصول على معلومات حول الأنواع في وقت التنفيذ.
\n\nفي هذه المقالة، سنتناول:
\n- \n
- الفرق بين التأمل والاستبطان؛ \n
- كيف يعمل RTTI (معلومات نوع وقت التنفيذ)؛ \n
- كيفية استخدام
typeidوdynamic_cast؛ \n - كيفية محاكاة التأمل باستخدام القوالب والماكرو؛ \n
- البدائل الحديثة (C++20/23) والمكتبات. \n
هل أنت مستعد للنظر في "المرآة" الخاصة بـ C++؟ هيا بنا!
\n\nالاستبطان مقابل التأمل: ما الفرق؟
\n\nقبل كتابة الكود، دعنا نضع النقاط على الحروف.
\n\n- \n
- الاستبطان - هو قدرة البرنامج على "النظر إلى نفسه" والحصول على معلومات حول الأنواع، ولكن دون تعديل هيكل البرنامج. على سبيل المثال: "معرفة اسم فئة الكائن". هذا موجود في C++ عبر RTTI. \n
- التأمل - هو الاستبطان + القدرة على إنشاء الكائنات ديناميكيًا، واستدعاء الدوال، وتعديل الحقول. هذا غير موجود في معيار C++ (حتى الآن). \n
في C++، نستخدم مصطلح "التأمل" غالبًا كهدف، و"الاستبطان" كأداة متاحة. لكن لا تقلق: حتى مع الوسائل المحدودة، يمكنك القيام بأشياء رائعة.
\n\nRTTI (معلومات نوع وقت التنفيذ) - أساس الاستبطان في C++
\n\nRTTI هي آلية تسمح بتحديد نوع الكائن أثناء وقت التنفيذ. يتم تفعيلها افتراضيًا (على الرغم من تعطيلها في بعض الأنظمة المضمنة باستخدام العلم -fno-rtti).
العناصر الرئيسية لـ RTTI:
\n- \n
typeid- عامل يعيدstd::type_info؛ \n dynamic_cast- تحويل آمن للأنواع متعددة الأشكال. \n
مثال 1: استخدام typeid
\n\n#include <iostream>\n#include <typeinfo>\n\nclass Base { virtual void foo() {} };\nclass Derived : public Base {};\n\nint main() {\n Base* b = new Derived();\n std::cout << "نوع الكائن: " << typeid(*b).name() << std::endl;\n delete b;\n return 0;\n}\n\nالإخراج (يعتمد على المترجم): class Derived أو 7Derived (اسم مشوه).
لاحظ: typeid يعمل فقط مع الفئات متعددة الأشكال (التي تحتوي على دالة افتراضية واحدة على الأقل). إذا أزلت virtual، ستحصل على نوع المؤشر، وليس الكائن الحقيقي.
مثال 2: dynamic_cast - تحويل آمن
\n\n#include <iostream>\n\nclass Animal { public: virtual ~Animal() = default; };\nclass Dog : public Animal { public: void bark() { std::cout << "Woof!\\n"; } };\nclass Cat : public Animal { public: void meow() { std::cout << "Meow!\\n"; } };\n\nvoid makeSound(Animal* a) {\n if (Dog* dog = dynamic_cast<Dog*>(a)) {\n dog->bark();\n } else if (Cat* cat = dynamic_cast<Cat*>(a)) {\n cat->meow();\n } else {\n std::cout << "حيوان غير معروف\\n";\n }\n}\n\nint main() {\n Animal* pets[] = { new Dog(), new Cat(), new Animal() };\n for (auto p : pets) makeSound(p);\n // لا تنس الحذف!\n for (auto p : pets) delete p;\n return 0;\n}\n\nهذا مثال كلاسيكي على الاستبطان: لا نعرف بالضبط أي كائن وصل، ولكن يمكننا التحقق من نوعه والتصرف وفقًا لذلك.