معلومات نوع وقت التشغيل
في برمجة الحاسوب ، تُعدّ معلومات نوع البيانات أثناء التشغيل، أو تحديد نوع البيانات أثناء التشغيل ( RTTI ) [ 1 ] ، ميزةً في بعض لغات البرمجة (مثل C++ [ 2 ]، و Object Pascal ، و Ada [ 3 ] )، حيث تُتيح هذه المعلومات معرفة نوع بيانات الكائن أثناء التشغيل . قد تكون معلومات نوع البيانات أثناء التشغيل متاحةً لجميع الأنواع، أو فقط للأنواع التي تُحددها صراحةً (كما هو الحال في Ada). تُعتبر معلومات نوع البيانات أثناء التشغيل تخصصًا لمفهوم أعمّ يُسمى استبطان النوع .
في تصميم لغة C++ الأصلي، لم يقم بيارن ستروستروب بتضمين معلومات نوع وقت التشغيل، لأنه كان يعتقد أن هذه الآلية غالباً ما تُساء استخدامها. [ 4 ]
ملخص
في لغة C++، يُمكن استخدام RTTI لإجراء تحويلات أنواع آمنة باستخدام dynamic_cast<T>المعامل، ولمعالجة معلومات النوع أثناء التشغيل باستخدام typeidالمعامل std::type_infoوالفئة، التي تخزن بدورها معلومات وقت التشغيل. في لغة Object Pascal، يُمكن استخدام RTTI لإجراء تحويلات أنواع آمنة باستخدام asالمعامل، واختبار الفئة التي ينتمي إليها الكائن باستخدام isالمعامل، ومعالجة معلومات النوع أثناء التشغيل باستخدام الفئات الموجودة في الوحدة RTTI[ 5 ] ( مثل الفئات: TRttiContext، TRttiInstanceType، إلخ). في لغة Ada، تخزن الكائنات ذات الأنواع الموسومة أيضًا علامة نوع، مما يسمح بتحديد نوع هذه الكائنات أثناء التشغيل. inيُمكن استخدام المعامل لاختبار ما إذا كان الكائن من نوع معين أثناء التشغيل، وما إذا كان يُمكن تحويله إليه بأمان. [ 6 ]
تتوفر تقنية RTTI فقط للفئات متعددة الأشكال ، أي التي تحتوي على دالة افتراضية واحدة على الأقل . عمليًا، لا يُعد هذا قيدًا، لأن الفئات الأساسية يجب أن تحتوي على دالة تدمير افتراضية لتمكين كائنات الفئات المشتقة من إجراء عملية تنظيف سليمة في حال حذفها من مؤشر الفئة الأساسية.
تحتوي بعض المترجمات على علامات لتعطيل RTTI. قد يؤدي استخدام هذه العلامات إلى تقليل الحجم الإجمالي للتطبيق، مما يجعلها مفيدة بشكل خاص عند استهداف الأنظمة ذات الذاكرة المحدودة. [ 7 ]
typeidعامل C++
تُستخدم الكلمة typeidالمفتاحية لتحديد فئة الكائن أثناء التشغيل. تُعيد هذه الكلمة مرجعًا إلى الكائن، والذي يبقى موجودًا حتى نهاية البرنامج. [ 8 ] يُفضّل استخدام هذه الكلمة المفتاحية ، في سياق غير متعدد الأشكال، على استخدام ` this` في الحالات التي تتطلب معلومات الفئة فقط، لأن ` this` إجراء ثابت الوقت دائمًا ، بينما قد تحتاج `this` إلى اجتياز شبكة اشتقاق الفئة لوسيطها أثناء التشغيل. بعض جوانب الكائن المُعاد مُحددة من قِبل التنفيذ، مثل `this` ، ولا يُمكن الاعتماد على اتساقها بين المُترجمات.std::type_infotypeiddynamic_cast<T>typeiddynamic_caststd::type_info::name()
std::bad_typeidيُثار استثناء عندما يكون التعبير typeid()ناتجًا عن تطبيق *عامل أحادي على مؤشر فارغ . أما بالنسبة لوسائط مرجعية فارغة أخرى، فيعتمد حدوث الاستثناء على التنفيذ. بعبارة أخرى، لضمان حدوث الاستثناء، يجب أن يأخذ التعبير الشكل التالي: typeid(*p)حيث pيمثل أي تعبير ينتج عنه مؤشر فارغ.
إذا استُخدم هذا typeidالمعامل في سياق std::type_infoغير مرئي، فإنه يكون غير دقيق. وهو مُعرَّف في ملف الرأس <typeinfo>(أو الوحدةstd ).
يوفر C++26 دعمًا متزايدًا لفحص النوع الانعكاسي من خلال النوع std::meta::info، ولكن يتم ذلك في وقت الترجمة . [ 9 ]
مثال
استيراد std ؛باستخدام std :: bad_typeid ؛ باستخدام std :: type_info ؛class Person { public : virtual ~ Person () = default ; };class Employee : public Person { // ... };int main () { Person person ; Employee employee ; Person * ptr = & employee ; Person & ref = employee ;type_info personType = typeid ( person ); type_info employeeType = typeid ( employee ); type_info ptrType = typeid ( ptr ); type_info refType = typeid ( ref ); // السلسلة التي تُرجعها الدالة std::type_info::name() مُحددة من قِبل التنفيذ.std :: println ( "{}" , personType.name ( )); // Person (statically known at compiletime) .std :: println ( "{}" , employeeType.name ( )); // Employee (statically known at compiletime) .std :: println ( "{}" , ptrType.name ( ) ); // Person* (مُعرف بشكل ثابت في وقت الترجمة).std :: println ( "{}" , refType.name ( ) ); // Employee // (يتم البحث عنه ديناميكيًا أثناء التشغيل // لأنه يمثل إلغاء مرجعية // لمؤشر إلى فئة متعددة الأشكال). std :: println ( "{ } " , typeid ( ref ) .name ()); // Employee (يمكن أن تكون المراجع متعددة الأشكال أيضًا)Person * p = nullptr ; try { typeid ( * p ); // سلوك غير مُعرَّف؛ يُطلق std::bad_typeid. } catch ( const bad_typeid & e ) { std :: println ( stderr , "تم رصد استثناء: {}" , e . what ()); }Person & ref2 = * p ; // سلوك غير مُعرَّف: محاولة الوصول إلى قيمة فارغة (null)// لا يستوفي هذا الشرط لإصدار خطأ std::bad_typeid // لأن تعبير typeid ليس نتيجة // تطبيق عامل الضرب الأحادي *. type_info ref2Type = typeid ( ref2 ); }الناتج (يختلف الناتج الدقيق باختلاف النظام والمترجم ):
شخص موظف شخص* موظف موظف
C++ dynamic_castو Java cast
يُستخدم عامل dynamic_castالتحويل في لغة C++ لتحويل مرجع أو مؤشر إلى نوع أكثر تحديدًا في التسلسل الهرمي للفئات . على عكس التحويلات الأخرى ، يجب أن يكون static_castهدف التحويل مؤشرًا أو مرجعًا إلى الفئة . على عكس التحويلات الأخرى على نمط لغة C (حيث يتم التحقق من النوع أثناء الترجمة)، يتم إجراء فحص أمان النوع في وقت التشغيل. إذا كانت الأنواع غير متوافقة، فسيتم طرح استثناء (عند التعامل مع المراجع ) أو سيتم إرجاع مؤشر فارغ (عند التعامل مع المؤشرات ).dynamic_caststatic_cast
يتصرف تحويل النوع في جافا بشكل مشابه؛ فإذا لم يكن الكائن المراد تحويله في الواقع نسخة من النوع المستهدف، ولا يمكن تحويله إلى واحد بواسطة طريقة معرفة في اللغة، java.lang.ClassCastExceptionفسيتم طرح استثناء. [ 10 ]
مثال
لنفترض أن دالة ما تأخذ كائنًا من نوع معين Baseكوسيط لها، وترغب في إجراء عملية إضافية إذا كان الكائن المُمرر نسخة من Derivedفئة فرعية من Base. يمكن القيام بذلك باستخدام dynamic_castما يلي.
استيراد std ؛باستخدام std :: array ؛ باستخدام std :: bad_cast ؛ باستخدام std :: unique_ptr ؛class Base { private : void specificToBase () const { std :: println ( "تم استدعاء دالة خاصة بالفئة Base" ); } public : // بما أن RTTI مُضمن في جدول الدوال الافتراضية، فيجب أن يكون هناك // دالة افتراضية واحدة على الأقل. virtual ~ Base () = default ; };class Derived : public Base { public : void specificToDerived () const { std :: println ( "تم استدعاء طريقة خاصة بـ B" ); } };void myFunction ( Base & base ) { try { // سينجح التحويل فقط لكائنات النوع B. Derived & derived = dynamic_cast < Derived &> ( base ); derived . specificToDerived (); } catch ( const bad_cast & e ) { std :: println ( stderr , "تم طرح استثناء {}." , e . what ()); std :: println ( "الكائن ليس من النوع Derived" ); } }int main ( int argc , char * argv []) { // مصفوفة من المؤشرات إلى الفئة الأساسية A. array < unique_ptr < Base > , 3 > arrayOfBase = { std :: make_unique < Derived > (); // مؤشر إلى كائن Derived. std :: make_unique < Derived > (); // مؤشر إلى كائن Derived. std :: make_unique < Base > (); // مؤشر إلى كائن Base. }for ( Base b : arrayOfBase ) { myFunction ( * b ); } return 0 ; }مخرجات وحدة التحكم:
تم استدعاء طريقة خاصة بالمشتقات تم استدعاء طريقة خاصة بالمشتقات تم طرح استثناء std::bad_cast. الكائن ليس من النوع المشتق
myFunctionيمكن كتابة نسخة مشابهة باستخدام المؤشرات بدلاً من المراجع :
void myFunction ( Base * base ) { Derived * derived = dynamic_cast < Derived *> ( base );إذا كان ( مشتق ) { مشتق- > خاص_بالنوع_المشتق (); } وإلا { std :: println ( stderr , "الكائن ليس من النوع المشتق" ); } }أوبجكت باسكال، دلفي
في لغتي Object Pascal و Delphi ، يُستخدم عامل isالتحقق من نوع الصنف أثناء التشغيل. يختبر هذا العامل انتماء كائن إلى صنف معين، بما في ذلك أصناف الأسلاف الفردية الموجودة في شجرة التسلسل الهرمي للوراثة (على سبيل المثال، Button1 هو صنف TButton له أسلاف: TWinControl → TControl → TComponent → TPersistent → TObject ، حيث يمثل الأخير سلف جميع الأصناف). asيُستخدم عامل التحقق من نوع الصنف عندما يلزم التعامل مع كائن أثناء التشغيل كما لو كان ينتمي إلى صنف سلف.
تُستخدم وحدة RTTI لمعالجة معلومات نوع الكائن أثناء التشغيل. تحتوي هذه الوحدة على مجموعة من الفئات التي تُمكّنك من: الحصول على معلومات حول فئة الكائن وأسلافه، وخصائصه، وأساليبه، وأحداثه، وتغيير قيم الخصائص، واستدعاء الأساليب. يوضح المثال التالي استخدام وحدة RTTI للحصول على معلومات حول الفئة التي ينتمي إليها الكائن، وإنشائه، واستدعاء أسلوبه. يفترض المثال أن الفئة TSubject قد تم تعريفها في وحدة باسم SubjectUnit.
يستخدم RTTI ، وSubjectUnit ؛إجراء بدون انعكاس ؛ متغير MySubject : TSubject ؛ ابدأ MySubject : = TSubject.Create ؛ حاول Subject.Hello ؛ أخيرًا Subject.Free ؛ نهاية ؛ نهاية ؛إجراء WithReflection ؛ متغير RttiContext : TRttiContext ؛ متغير RttiType : TRttiInstanceType ؛ متغير Subject : TObject ؛ ابدأ RttiType := RttiContext.FindType ( 'SubjectUnit.TSubject' ) as TRttiInstanceType ؛ متغير Subject : = RttiType.GetMethod ( ' Create' ) . Invoke ( RttiType.MetaclassType , [ ] ) . AsObject ؛ حاول RttiType.GetMethod ( ' Hello ' ) . Invoke ( Subject , [ ] ) ؛ أخيرًا Subject.Free ؛ نهاية ؛ نهاية ؛انظر أيضاً
مراجع
- ↑ صن مايكروسيستمز (2000). "تحديد نوع وقت التشغيل" . دليل برمجة C++ . أوراكل . تم الاطلاع عليه بتاريخ 16 أبريل 2015 .
- ↑ "مكتبة دعم اللغة [ support.rtti ] " . eel.is. تم الاسترجاع في 13 يوليو 2021 .
- ↑ "البرمجة الكائنية التوجه" . learn.adacore.com . تم الاطلاع عليه بتاريخ 13 يوليو 2021 .
- ^ بيارن ستروستروب (مارس 1993). "تاريخ لغة C++: 1979-1991" (PDF) . بيارن ستروستروب. ص. 50 . تم الاسترجاع 2009-05-18 .
- ↑ "العمل مع RTTI - RAD Studio" . docwiki.embarcadero.com . تم الاطلاع عليه بتاريخ 2021-06-06 .
- ↑ إنجلش، جون (22-02-2002). "الفصل 15". آدا 95: فن البرمجة الكائنية التوجه . تم الاسترجاع في 13-07-2021 .
- ↑ "تجنب RTTI، ودعم -fno-rtti في مُصرّف Arm 6" . مطور Arm . تم الاسترجاع في 13 يوليو 2021 .
- ↑ معيار C++ (ISO/IEC14882) القسم 5.2.8 [expr.typeid]، 18.5.1 [lib.type.info] – http://cs.nyu.edu/courses/fall11/CSCI-GA.2110-003/documents/c++2003std.pdf
- ↑ cppreference.com. "رأس المكتبة القياسية <meta /> (C++26)" . cppreference.com . cppreference.com . تم الاطلاع عليه بتاريخ 17 مايو 2026 .
- ↑ "ClassCastException (Java Platform SE 8)" .
روابط خارجية
- لغة سي++
- الفصل الدراسي (برمجة الحاسوب)
- أنواع البيانات
- مقارنات لغات البرمجة
