التحويل الديناميكي (Dynamic Casting) و RTTI
جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 62 من 104.
أحيانًا عند العمل مع تعدد الأشكال، تحتاج إلى تحديد النوع الفعلي لكائن أثناء وقت التشغيل أو تحويل مؤشر فئة أساسية بأمان إلى مؤشر فئة مشتقة. توفّر C++ RTTI (معلومات النوع في وقت التشغيل) وdynamic_cast لهذه الحالات.
تحوّل dynamic_cast المؤشرات أو المراجع بأمان ضمن تسلسل هرمي للوراثة. وعلى خلاف static_cast، تُجري فحصًا أثناء التشغيل وتُعيد nullptr إذا كان التحويل غير صالح:
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() { std::cout << "Woof!" << std::endl; }
};
class Cat : public Animal {};
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // ينجح: يعيد مؤشراً صالحاً
if (dog) {
dog->bark(); // آمن لاستدعاء الدالة الخاصة بـ Dog
}
Cat* cat = dynamic_cast<Cat*>(animal); // يفشل: يعيد nullptrمهم: يعمل dynamic_cast فقط مع الأنواع متعددة الأشكال (الفئات التي تحتوي على دالة افتراضية واحدة على الأقل). يتيح لك العامل typeid الاستعلام عن النوع الفعلي لكائن ما:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // يطبع معلومات النوع لـ Dogعلى الرغم من أن dynamic_cast مفيد، فإن استخدامه المتكرر غالبًا ما يشير إلى مشكلة في التصميم. فضّل الدوال الافتراضية متى أمكن، إذ تتيح للكائن التعامل مع السلوك الخاص بالنوع دون إجراء فحص صريح للنوع.
التحدي
سهللنَبْنِ نظامًا لفحص المركبات يستخدم dynamic_cast للتعرّف بأمان على أنواع المركبات المختلفة والتفاعل معها. ستنشئ تسلسلاً هرميًا للمركبات، حيث يحتاج المفتش إلى إجراء فحوصات خاصة بالنوع لا تدعمها إلا مركبات معينة.
ستنظّم الشيفرة عبر ثلاثة ملفات:
Vehicle.h: عرّف الفئة الأساسيةVehicleالتي تمثّل أي مركبة في النظام:- عضو
std::string licensePlateمحمي - مُنشئ يقوم بتهيئة لوحة الترخيص
- دالة افتراضية
getDescription()تُرجع"Vehicle: <licensePlate>" - مُدمّر افتراضي
- عضو
Vehicles.h: عرّف ثلاثة أنواع من المركبات المشتقة:Car:- عضو
int seatCountخاص - مُنشئ يستقبل لوحة الترخيص وعدد المقاعد
- أعد تعريف
getDescription()لتُرجع"Car: <licensePlate>" - دالة
inspectSeatbelts()تطبع:Inspecting <seatCount> seatbelts in <licensePlate>
Truck:- عضو
double cargoCapacityخاص (بالأطنان) - مُنشئ يستقبل لوحة الترخيص وسعة الحمولة
- أعد تعريف
getDescription()لتُرجع"Truck: <licensePlate>" - دالة
inspectCargo()تطبع:Inspecting cargo area (<cargoCapacity> tons) in <licensePlate>
Motorcycle:- عضو
bool hasSidecarخاص - مُنشئ يستقبل لوحة الترخيص وحالة العربة الجانبية
- أعد تعريف
getDescription()لتُرجع"Motorcycle: <licensePlate>" - دالة
inspectHelmetStorage()تطبع:Inspecting helmet storage in <licensePlate>إذا كانت تحتوي على عربة جانبية، أوNo helmet storage in <licensePlate>إذا لم تكن كذلك
- عضو
main.cpp: اقرأ ثلاث مدخلات (كل منها في سطر منفصل):- لوحة ترخيص
Car - لوحة ترخيص
Truck - لوحة ترخيص
Motorcycle
أنشئ
Carبأربعة مقاعد، وTruckبسعة 10.5 أطنان، وMotorcycleمزودة بعربة جانبية. خزّن المركبات الثلاث جميعها في مصفوفة من مؤشراتVehicle*.كرّر عبر المصفوفة، ولكل مركبة:
- اطبع وصفها باستخدام
getDescription() - استخدم
dynamic_castلمحاولة التحويل إلى كل نوع مشتق - إذا نجح التحويل إلى
Car*، فاستدعِinspectSeatbelts() - إذا نجح التحويل إلى
Truck*، فاستدعِinspectCargo() - إذا نجح التحويل إلى
Motorcycle*، فاستدعِinspectHelmetStorage()
اطبع سطرًا فارغًا بين فحص كل مركبة والمركبة التي تليها. نظّف الكائنات المخصّصة ديناميكيًا عند الانتهاء.
- لوحة ترخيص
على سبيل المثال، مع المدخلات ABC-123 وTRK-456 وMTR-789:
Car: ABC-123
Inspecting 4 seatbelts in ABC-123
Truck: TRK-456
Inspecting cargo area (10.5 tons) in TRK-456
Motorcycle: MTR-789
Inspecting helmet storage in MTR-789لاحظ أن dynamic_cast يُرجع مؤشرًا صالحًا فقط عندما يطابق نوع الكائن الفعلي النوع المستهدف. ولكل مركبة، سينجح تحويل واحد فقط من التحويلات الثلاثة، مما يتيح لك استدعاء دالة الفحص الخاصة بالنوع بأمان. هذه هي قوة RTTI: تحديد النوع الفعلي أثناء التشغيل والتصرّف وفقًا لذلك.
جرّب بنفسك
#include <iostream>
#include <string>
#include "Vehicle.h"
#include "Vehicles.h"
using namespace std;
int main() {
// اقرأ المدخلات
string carPlate, truckPlate, motorcyclePlate;
cin >> carPlate;
cin >> truckPlate;
cin >> motorcyclePlate;
// TODO: أنشئ Car بـ 4 مقاعد
// TODO: أنشئ Truck بسعة 10.5 طن
// TODO: أنشئ Motorcycle مع sidecar (true)
// TODO: خزّن الثلاثة جميعها في مصفوفة من مؤشرات Vehicle*
// TODO: مرّ عبر المصفوفة ولكل مركبة:
// 1. اطبع وصفها باستخدام getDescription()
// 2. استخدم dynamic_cast لمحاولة التحويل إلى كل نوع مشتق
// 3. إذا نجح التحويل إلى Car*، استدعِ inspectSeatbelts()
// 4. إذا نجح التحويل إلى Truck*، استدعِ inspectCargo()
// 5. If cast to Motorcycle* succeeds, call inspectHelmetStorage()
// 6. اطبع سطرًا فارغًا بين فحص كل مركبة
// TODO: نظّف الكائنات المخصصة ديناميكيًا
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس البرمجة كائنية التوجه
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامةتدرّب بنفسك: مترجم C++ عبر الإنترنت