חריגות ב ++C

עריכה

הקדמה

עריכה

חריגות ( exceptions באנגלית ) ב ++C הן המנגנון שמאפשר לטפל בשגיאות או בחריגות מהאפיון של המערכת(מכאן נובע השם). בעזרתן, ניתן בכל רגע נתון, לעצור את הריצה של התוכנית, לצאת מ"הסקופ" ולבצע קוד שניכתב מראש כדי לטפל במצב השגיאה. חריגות ב ++C מזוהות על ידי קוד בדיקה שכותב המתכנת בתוך בלוק בדיקה שמוגדר באמצעות הפקודה TRY או בכינון הפונקציות הנקראות מתוך בלוק הבדיקה. כאשר תנאי הבדיקה לא מתקיימים יכול המתכנת ליזום חריגה בעזרת הפקודה THROW. הקוד לטיפול בחריגה יוגדר באמצעות פקודת CATCH בסיום בלוק הבדיקה. לאחר חריגה יקרא קוד הטיפול הקרוב למקום "לזריקת" החריגה.
כל שורת קוד שהמתכנת כותב מתורגמת לשפת מעבד ( אסמבלי או לאופ-קודים בשכבה נמוכה יותר ) שאותן המעבד מבצע לפי הסדר. ברגע שנמצאת חריגה/שגיאה, המעבד מקבל פקודה ( אופ-קוד ) שאומרת לו "זוהי שגיאה" ( איך זה קורה, נתאר בהמשך )ואז הוא מתחיל בטיפול בשגיאה. מה שקורה מרגע זה עובד פחות או יותר כך:

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

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

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

חריגות, הן לא דבר רע. להיפך - הן הדרך שלנו להתגונן בפני דברים רעים שעלולים לקרות,

מי יכול "לזרוק"?

עריכה

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

  • זיכרון שאפשר לקרוא ממנו.
  • זיכרון שאפשר לכתוב אליו.
  • זיכרון שאפשר להריץ ממנו קוד.

אם יש ניסיון לעשות משהו שאסור לעשות על קטע זיכרון מסויים, מערכת ההפעלה תתריע ו"תזרוק" שגיאה. נקרא לה בינתיים Access Violation.

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

מה אפשר לזרוק

עריכה

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

נביט בדוגמא הבאה:

void throwingFunction()
{
   throw "This is the exception!"; 
}
int main()
{
   try {
      throwingFunction();
   }
   catch(const char* string) {
      cout << "Caught it:" << string << endl;
   }
}

הסבר:

  • הפונקציה throwingFunction זורקת מחרוזת ע"י שימוש בפקודה throw.
  • פונקצית ה ()main שלנו עושה שימוש בבלוק מהצורה:
   try { }
   catch(type) { }

הבלוק הזה מאפשר לנו לתפוס חריגות שמתרחשות בתוך הסקופ של {}try במידה ואמרנו שאנחנו יודעים להתמודד עם הטיפוס של החריגה שנזרקה.

שפות אחרות

עריכה

חריגות הן לא משהו ייחודי לשפת ++C וגם בשפות אחרות יש שימוש במנגנון זה. למשל - כך היינו עושים את זה בשפת C. נניח שאנו ממשים את הפונקציה sqrt המחזירה שורש של מספר. חתימת הפונקציה:

 double sqrt(double num);


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

 double sqrt(double num){
   if(num < 0.0) {
     return -1.0;
   }
// Do the rest of the root
  )

והקוד שמשתמש בפונקציה, היה נראה כך:

double sqrtNum = sqrt(num);
if (sqrtNum < 0) {
// handler error...
}

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

חריגות

עריכה

פרק זה לוקה בחסר. אתם מוזמנים לתרום לוויקיספר ולהשלים אותו. ראו פירוט בדף השיחה.



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

מה בעצם ניתן לזרוק ? אובייקטים ופרימיטיבים של C++. כדי לזרוק חריגה משתמשים במילת המפתח throw. נדגים זאת כעת.

זריקת חריגה

עריכה

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

class InvalidArgumentException{};

כעת, נממש מחדש את sqrt:


 double sqrt(double num){
   if(num < 0.0) {
    throw InvalidArgumentException{};
   }
// Do the rest of the root
  )


הפרק הקודם:
מרחבי שם
חריגות הפרק הבא:
פקודות לקדם-מעבד