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

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

challenge icon

التحدي

سهل

لنَبْنِ نظامًا لفحص المركبات يستخدم 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: اقرأ ثلاث مدخلات (كل منها في سطر منفصل):
    1. لوحة ترخيص Car
    2. لوحة ترخيص Truck
    3. لوحة ترخيص Motorcycle

    أنشئ Car بأربعة مقاعد، و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: تحديد النوع الفعلي أثناء التشغيل والتصرّف وفقًا لذلك.

جرّب بنفسك

#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;
}
quiz iconاختبر نفسك

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

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت