التأمل والاستبطان في C++: كيف تجعل الكود يرى نفسه

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

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

ابدأ الدورة

ما هو التأمل والاستبطان في C++ ولماذا نحتاجهما؟

\n\n

تخيل أنك تكتب محرك ألعاب معقدًا أو إطار عمل لمعالجة البيانات. في مرحلة ما، تحتاج إلى فهم: "ما هذا الكائن بالضبط؟ ما هي دواله؟ هل لديه حقل name؟"

\n\n

في لغات مثل Python أو Java، يتم ذلك بسطر واحد - حيث يوجد تأمل كامل (قدرة البرنامج على دراسة وتعديل هيكله أثناء وقت التنفيذ). أما في C++ فالأمر أكثر تعقيدًا. C++ هي لغة ذات كتابة ثابتة قوية، ولا يوجد تأمل "خارج الصندوق" تقريبًا. ولكن يوجد استبطان - قدرة محدودة على الحصول على معلومات حول الأنواع في وقت التنفيذ.

\n\n

في هذه المقالة، سنتناول:

\n
    \n
  • الفرق بين التأمل والاستبطان؛
  • \n
  • كيف يعمل RTTI (معلومات نوع وقت التنفيذ)؛
  • \n
  • كيفية استخدام typeid و dynamic_cast؛
  • \n
  • كيفية محاكاة التأمل باستخدام القوالب والماكرو؛
  • \n
  • البدائل الحديثة (C++20/23) والمكتبات.
  • \n
\n\n

هل أنت مستعد للنظر في "المرآة" الخاصة بـ C++؟ هيا بنا!

\n\n

الاستبطان مقابل التأمل: ما الفرق؟

\n\n

قبل كتابة الكود، دعنا نضع النقاط على الحروف.

\n\n
    \n
  • الاستبطان - هو قدرة البرنامج على "النظر إلى نفسه" والحصول على معلومات حول الأنواع، ولكن دون تعديل هيكل البرنامج. على سبيل المثال: "معرفة اسم فئة الكائن". هذا موجود في C++ عبر RTTI.
  • \n
  • التأمل - هو الاستبطان + القدرة على إنشاء الكائنات ديناميكيًا، واستدعاء الدوال، وتعديل الحقول. هذا غير موجود في معيار C++ (حتى الآن).
  • \n
\n\n

في C++، نستخدم مصطلح "التأمل" غالبًا كهدف، و"الاستبطان" كأداة متاحة. لكن لا تقلق: حتى مع الوسائل المحدودة، يمكنك القيام بأشياء رائعة.

\n\n

RTTI (معلومات نوع وقت التنفيذ) - أساس الاستبطان في C++

\n\n

RTTI هي آلية تسمح بتحديد نوع الكائن أثناء وقت التنفيذ. يتم تفعيلها افتراضيًا (على الرغم من تعطيلها في بعض الأنظمة المضمنة باستخدام العلم -fno-rtti).

\n\n

العناصر الرئيسية لـ RTTI:

\n
    \n
  • typeid - عامل يعيد std::type_info؛
  • \n
  • dynamic_cast - تحويل آمن للأنواع متعددة الأشكال.
  • \n
\n\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 (اسم مشوه).

\n\n

لاحظ: typeid يعمل فقط مع الفئات متعددة الأشكال (التي تحتوي على دالة افتراضية واحدة على الأقل). إذا أزلت virtual، ستحصل على نوع المؤشر، وليس الكائن الحقيقي.

\n\n

مثال 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

هذا مثال كلاسيكي على الاستبطان: لا نعرف بالضبط أي كائن وصل، ولكن يمكننا التحقق من نوعه والتصرف وفقًا لذلك.

المدونات

توصيات الكتب