Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

التحويل الديناميكي (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) عندما يكون ذلك ممكناً، لأنها تسمح للكائن بالتعامل مع السلوك الخاص بالنوع دون الحاجة إلى التحقق الصريح من النوع.

challenge icon

التحدي

سهل

لنقم ببناء نظام فحص للمركبات يستخدم dynamic_cast لتحديد أنواع المركبات المختلفة والتفاعل معها بأمان. ستقوم بإنشاء تسلسل هرمي للمركبات حيث يحتاج المفتش إلى إجراء فحوصات خاصة بالنوع لا تدعمها إلا مركبات معينة.

ستقوم بتنظيم الكود الخاص بك عبر ثلاثة ملفات:

  • Vehicle.h: قم بتعريف فئة أساسية Vehicle تمثل أي مركبة في النظام:
    • عضو محمي (protected) باسم std::string licensePlate
    • منشئ (constructor) يقوم بتهيئة لوحة الترخيص
    • دالة افتراضية getDescription() تعيد "Vehicle: <licensePlate>"
    • دالة تدمير (destructor) افتراضية
  • 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> إذا لم تكن تحتوي عليها
  • main.cpp: اقرأ ثلاثة مدخلات (كل منها في سطر منفصل):
    1. لوحة ترخيص السيارة (Car)
    2. لوحة ترخيص الشاحنة (Truck)
    3. لوحة ترخيص الدراجة النارية (Motorcycle)

    قم بإنشاء Car بـ 4 مقاعد، و Truck بسعة 10.5 طن، و Motorcycle بعربة جانبية. قم بتخزين الثلاثة في مصفوفة من مؤشرات Vehicle*.

    قم بالمرور عبر المصفوفة ولكل مركبة:

    1. اطبع وصفها باستخدام getDescription()
    2. استخدم dynamic_cast لمحاولة التحويل إلى كل نوع مشتق
    3. إذا نجح التحويل إلى Car*، فاستدعِ inspectSeatbelts()
    4. إذا نجح التحويل إلى Truck*، فاستدعِ inspectCargo()
    5. إذا نجح التحويل إلى 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;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس Object Oriented Programming