التحويل الديناميكي (Dynamic Casting) و RTTI
جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 62 من 104.
أحياناً عند العمل مع تعدد الأشكال (polymorphism)، تحتاج إلى تحديد النوع الفعلي للكائن في وقت التشغيل أو تحويل مؤشر الفئة الأساسية (base class pointer) بأمان إلى مؤشر فئة مشتقة (derived class pointer). توفر لغة C++ خاصية RTTI (Runtime Type Information) و dynamic_cast لهذه الحالات.
يقوم dynamic_cast بتحويل المؤشرات أو المراجع بأمان داخل تسلسل هرمي للوراثة. وعلى عكس static_cast، فإنه يقوم بإجراء فحص في وقت التشغيل (runtime check) ويعيد 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 مفيد، إلا أن الاستخدام المتكرر غالباً ما يشير إلى مشكلة في التصميم. فضل استخدام الدوال الافتراضية (virtual functions) عندما يكون ذلك ممكناً، لأنها تسمح للكائن بالتعامل مع السلوك الخاص بالنوع دون الحاجة إلى التحقق الصريح من النوع.
التحدي
سهللنقم ببناء نظام فحص للمركبات يستخدم dynamic_cast لتحديد أنواع المركبات المختلفة والتفاعل معها بأمان. ستقوم بإنشاء تسلسل هرمي للمركبات حيث يحتاج المفتش إلى إجراء فحوصات خاصة بالنوع لا تدعمها إلا مركبات معينة.
ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:
Vehicle.h: قم بتعريف فئة أساسيةVehicleتمثل أي مركبة في النظام:- عضو محمي (protected) باسم
std::string licensePlate - منشئ (constructor) يقوم بتهيئة لوحة الترخيص
- دالة افتراضية
getDescription()تعيد"Vehicle: <licensePlate>" - دالة تدمير (destructor) افتراضية
- عضو محمي (protected) باسم
Vehicles.h: قم بتعريف ثلاثة أنواع مشتقة من المركبات:Car:- عضو خاص (private) باسم
int seatCount - منشئ يأخذ لوحة الترخيص وعدد المقاعد
- تجاوز (Override) للدالة
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 - منشئ يأخذ لوحة الترخيص وحالة العربة الجانبية (sidecar)
- تجاوز للدالة
getDescription()لتعيد"Motorcycle: <licensePlate>" - دالة
inspectHelmetStorage()تطبع:Inspecting helmet storage in <licensePlate>إذا كانت تحتوي على عربة جانبية، أوNo helmet storage in <licensePlate>إذا لم تكن تحتوي عليها
- عضو خاص (private) باسم
main.cpp: اقرأ ثلاثة مدخلات (كل منها في سطر منفصل):- لوحة ترخيص السيارة (Car)
- لوحة ترخيص الشاحنة (Truck)
- لوحة ترخيص الدراجة النارية (Motorcycle)
قم بإنشاء
Carبـ 4 مقاعد، و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 — تحديد النوع الفعلي في وقت التشغيل والتصرف بناءً على ذلك.
ورقة مرجعية
توفر لغة ++C ميزة RTTI (معلومات النوع في وقت التشغيل) و dynamic_cast لتحديد النوع الفعلي للكائن في وقت التشغيل وتحويل المؤشرات بأمان داخل هيكل الوراثة.
يقوم dynamic_cast بإجراء فحص في وقت التشغيل ويعيد nullptr إذا كان التحويل غير صالح:
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {
public:
void bark() { std::cout << "Woof!" << std::endl; }
};
Animal* animal = new Dog();
Dog* dog = dynamic_cast<Dog*>(animal); // Succeeds: returns valid pointer
if (dog) {
dog->bark(); // Safe to call Dog-specific method
}
Cat* cat = dynamic_cast<Cat*>(animal); // Fails: returns nullptrهام: يعمل dynamic_cast فقط مع الأنواع متعددة الأشكال (polymorphic types) - وهي الفئات التي تحتوي على دالة افتراضية (virtual function) واحدة على الأقل.
يقوم عامل التشغيل typeid بالاستعلام عن النوع الفعلي للكائن:
#include <typeinfo>
Animal* pet = new Dog();
std::cout << typeid(*pet).name() << std::endl; // Outputs type info for Dogعلى الرغم من فائدته، إلا أن الاستخدام المتكرر لـ dynamic_cast غالباً ما يشير إلى مشكلة في التصميم. يفضل استخدام الدوال الافتراضية (virtual functions) كلما أمكن ذلك.
جرّب بنفسك
#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 مع عربة جانبية (true)
// TODO: تخزين الثلاثة جميعاً في مصفوفة من مؤشرات Vehicle*
// TODO: المرور عبر المصفوفة ولكل مركبة:
// 1. طباعة وصفها باستخدام getDescription()
// 2. استخدام dynamic_cast لمحاولة التحويل إلى كل نوع مشتق
// 3. إذا نجح التحويل إلى Car*، استدعِ inspectSeatbelts()
// 4. إذا نجح التحويل إلى Truck*، استدعِ inspectCargo()
// 5. إذا نجح التحويل إلى Motorcycle*، استدعِ inspectHelmetStorage()
// 6. طباعة سطر فارغ بين فحص كل مركبة
// TODO: تنظيف الكائنات المخصصة ديناميكياً
return 0;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
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مراجعة - الحاويات العامة