Menu
Coddy logo textTech

המרה דינמית ו־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. לוחית הרישוי של המכונית
    2. לוחית הרישוי של המשאית
    3. לוחית הרישוי של האופנוע

    צרו 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: קביעת הסוג בפועל בזמן הריצה ופעולה בהתאם.

נסו בעצמכם

#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בחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין