C++/המרות

מתוך ויקיספר, אוסף הספרים והמדריכים החופשי
< C++

שפת C++ היא בעלת טיפוסיות חזקה (Strongly-typed), משמע הדבר שהמהדר אוסר עלינו להמיר בין טיפוסים רבים. לדוגמה, אם לא נציין במפורש (explicit), לא נוכל להמיר נקודה צפה לשלם כי יש סיכוי לאיבוד מידע חשוב, לא נוכל להמיר שלם למצביע כי הדבר חסר כל משמעות. מנגנון זה דומה ליחידות המדידה בפיזיקה, הוא עוזר למצוא שגיאות בתכנון התוכנית או בכתיבת הקוד.

המרת טיפוסים[עריכה]

כיוון ששפת C++ היא הרחבה לשפת C, ניתן להשתמש בהמרות בסגנון C. כמו כן נוספו המרות בסגנון קריאה לפונקציה (לבנאי) ו-4 אופרטורים חדשים מיוחדים.

סגנון C[עריכה]

כדי להמיר ערך x מטיפוס A לטיפוס B נרשום לפני ה-x את שם הטיפוס B בסוגריים עגולים. דוגמה המוכרת למתכנתי C היא:

int *p = (int*)malloc(sizeof(int));

הפונקציה malloc מחזירה טיפוס (void*), אותו המהדר לא מאפשר להמיר ל-(int*) אלא אם לא נציין זאת במפורש.

שימו לב, המהדר יאפשר לבצע המרה כזו רק אם הדבר בכלל אפשרי. לדוגמה טיפוס המוגדר על ידי המתכנת ניתן להמיר לשלם למשל, רק אם מוגדרת המרה כזו (ראה בהמשך). אם לא, תינתן הודעת שגיאה.

סגנון קריאה לפונקציה[עריכה]

כאשר נוספו ל-C++ הבנאים, נוספה האפשרות ליצור אובייקט זמני חסר שם בצורה הבאה:

complex foo = complex(1,2) + complex(2,3);

בדוגמה זו נוצרים שני אובייקטים זמניים מטיפוס complex ומאותחלים על ידי קריאה לבנאי מתאים. שני האובייקטים הזמניים מחוברים ונשמרים במשתנה בעל שם foo.

סגנון כתיבה זה, פירושו: "צור אובייקט מטיפוס B ואתחל באמצעות הפרמטרים הניתנים". כאשר יש לנו פרמטר אחד מטיפוס A נוכל להתיחס לפעולה זו כלהמרה של A ל-B:

A x = f();
B y = B(x);

שימו לב שלא כל כתיבה כזו היא קריאה לבנאי. יתכן שאין בנאי מתאים אך מועמס אופרטור המרה מתאים, כמו כן לטיפוסים המובנים אין בנאים:

int a = int(1.23456789);

אופרטורי ה-cast[עריכה]

ארבעת אופרטורים הללו הנם חידוש משמעותי בשפה. אופרטורים אלה מאפשרים לציין באופן מפורש את סוג ההמרה אותה אנו רוצים לבצע ולכן עדיפים על שני הסגנונות הקודמים.

אופרטורים אלה נראים בדומה לתבניות (לימדו בהמשך), את הטיפוס אליו אנו ממירים רושמים בין סוגריים משולשים ואת הערך בסוגריים עגולים: _cast<B>(x). שימוש רב באופרטורים אלה מגעיל את הקוד. ניתן לראות בזה את כוונתו של ממציא השפה להפחית את השימוש בהמרות, ובכן נהוג להימנע מהמרות מיותרות.

  • const_cast - משמש להמרה של קבוע ללא קבוע, כלומר "הורדת" ה-const. המרה זו מסוכנת כי אם האובייקט אכן נוצר כקבוע, המשך פעולת התכנית לא מוגדר (לפעמים שגיאה). להמרה זו יש משמעות כאשר המתכנת בטוח שהאובייקט הוא לא באמת קבוע.
המרה זו יש לעשות במקרים נדירים למדי, לרוב ניתן להשתמש במקומה ב-mutable או לוודא שלא הגזמנו בשימוש ב-const בפרמטרי הפונקציות.
int a = 0x2A;
const int b = 42;

const int *cp1 = &a;
int *p1 = const_cast<int*>(cp1);
++*p1; // בסדר

const int *cp2 = &b;
int *p2 = const_cast<int*>(cp2);
++*p2; // רעעע
  • reinterpret_cast - אומר למהדר להתעלם מהטיפוסים, בזה משמש להמרה בין משפחות שונות של טיפוסים, למשל מצביעים לשלמים. לרוב אופרטור זה יצור אובייקט בעל אותן הסיביות כמו האובייקט המקורי. כל האחריות על נכונות הערך החדש היא על המתכנת.
struct A { /* ... */ };
struct B { /* ... */ };

A *x = f();
B *y = reinterpret_cast<B*>(x);
  • static_cast - משמש להמרה בין טיפוסים דומים, מאותה משפחה (לדוגמה מספרים ממשיים לשלמים, מצביעים למחלקות בתוך היררכית מחלקות). המהדר בודק את נכונות ההמרה. המרה מסוג זה עלולה לגרום לאיבוד מידע (למשל double ל-int), אך לעומת ה-reinterpret_cast היא בעלת הגיון אותו מבין המהדר.
int y = static_cast<int>(sin(x)*128.0+128.0);
  • dynamic_cast - משמש להמרה בזמן ריצה. למד בהמשך בפרק על RTTI.

המרות טיפוסים של המתכנת[עריכה]

עד כאן הראינו כיצד לבטא ב-C++ את רצוננו להמיר טיפוס אחד לטיפוס אחר, אבל בפועל המרנו רק טיפוסים מובנים (מספרים, מצביעים). כנאמר כבר בספר זה, אחד הרעיונות בטמונים בשפת C++ הוא לתת אפשרות למתכנת ליצור טיפוסים אפקטיביים משלו שישתלבו כמו הטיפוסים המובנים. גם כאן נרצה להגדיר כיצד אנו רוצים להמיר בין מספר רציונלי לבין מספר עם נקודה צפה, בין תאריך בפורמט שהגדרנו בפרק על מחלקות לבין טיפוס time_t של C וכד'.

כדי לבצע המרה, נוכל כמובן לכתוב פונקציה שתקבל ערך מטיפוס אחד, ותחזיר ערך מטיפוס אחר. לעיתים נרצה להגדיר המרה שתעבוד כמו המרות ה-implicit או ה-explicit המובנות.

בנאים[עריכה]

כל המרה היא יצירת ערך חדש על בסיס ערך קיים. הדרך הפשוטה שאתם כבר בטח ניחשתם (ואם תרגלתם את החומר בפרקים הקודמים אז כנראה כבר השתמשתם בה), היא כתיבת בנאי המאתחל את המופע של המחלקה באמצעות הפרמטר. לדוגמה:

class rational {
    // c + a/b
    unsigned short a, b;
    long c;

public:
    rational(double x)
    {
        // קוד המייצג את המפרמטר בצורת שבר פשוט
    }
};

בנאי זה מגדיר את ההמרה של double לטיפוס שלנו rational (האלגוריתם טיפה ארוך אך תוכלו לכתוב אותו כתרגיל, רמז). כעת נוכל להשתמש בו בצורה הבאה:

rational a;
double c;
// ...
a = rational(c);

הערה: לא הכרחי לציין את ההמרה במפורש אלא אם כן המבנאי המתאים מוגדר כ-explicit.

העמסת אופרטורים[עריכה]

הבנאי יוצר מופע מחלקה על בסיס ערך מטיפוס אחר כלשהו. אין באפשרותנו להוסיף בנאי,למשל, ל"מחלקת int" (כי לא קיימת כזאת) אך נרצה להגדיר המרות גם לטיפוסים מובנים ולטיפוסים שאין לנו את קוד המקור שלהם. מסיבה זו ניתן להגדיר אופרטור המרה לכל טיפוס שנרצה כך:

class rational {
public:
    operator double () const {
        return c + (double)a/b;
    }
};

לפונקציות אופרטורים אלה אין צורך לציין את הטיפוס המוחזר כיוון שהוא חייב להיות זהה לזה שאליו אנו ממירים.

חשוב לזכור כי המרות אלה הן implicit, כלומר אנחנו לא חייבים לכתוב במפורש שאנו רוצים לעשות המרה:

rational a;
// ...
double c = a;

ריבוי המרות מהסוג הזה עלול לגרום לדו-משמעיות או להפתעות שלא תצפו להן, לכן כדאי להמעיט בהמרות אלה.


הפרק הקודם:
העמסת אופרטורים
המרות הפרק הבא:
פולימורפיזם