טיפול בחריגות ב-OOP
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 81 מתוך 104.
טיפול בחריגות ב-C++ מספק דרך מובנית לזהות שגיאות זמן ריצה ולהגיב להן. בתכנות מונחה עצמים, חריגות משתלבות באופן טבעי בהיררכיות של מחלקות, ומאפשרות לך ליצור סוגי שגיאות בעלי משמעות, שמכילים הקשר לגבי מה שהשתבש.
המנגנון הבסיסי משתמש בשלוש מילות מפתח: try, throw ו־catch. קוד שעלול להיכשל נמצא בתוך בלוק try, שגיאות מסומנות באמצעות throw, ובלוקי catch מטפלים בסוגים מסוימים של חריגות:
#include <iostream>
#include <stdexcept>
class BankAccount {
double balance;
public:
BankAccount(double b) : balance(b) {}
void withdraw(double amount) {
if (amount > balance) {
throw std::runtime_error("Insufficient funds");
}
balance -= amount;
}
double getBalance() const { return balance; }
};
int main() {
BankAccount account(100.0);
try {
account.withdraw(150.0);
} catch (const std::runtime_error& e) {
std::cout << "Error: " << e.what() << "\n";
}
std::cout << "Balance: " << account.getBalance() << "\n";
}הספרייה התקנית של C++ מספקת היררכיה של מחלקות חריגה, שהבסיס שלה הוא std::exception. מחלקות נגזרות נפוצות כוללות את std::runtime_error עבור בעיות בזמן ריצה ואת std::logic_error עבור טעויות תכנות. כל החריגות התקניות מספקות מתודת what() שמחזירה הודעת שגיאה.
כאשר נזרקת חריגה, מחסנית הקריאות משתחררת — נקראות פונקציות ההרס של כל האובייקטים המקומיים בזמן שהתוכנית מחפשת בלוק catch תואם. כך מובטח ניקוי תקין גם כשמתרחשות שגיאות, וזו הסיבה ש-RAII וטיפול בחריגות עובדים היטב כל כך יחד.
אתגר
קלבואו נבנה מחשבון בטוח לחלוקה, שמדגים טיפול בחריגות בהקשר מונחה עצמים. תיצרו מחלקה בשם Calculator שתבצע פעולות חילוק ותזרוק חריגות משמעותיות כשמשהו משתבש, ואז תטפלו בחריגות האלה בצורה מסודרת בתוכנית הראשית.
תארגנו את הקוד שלכם בשלושה קבצים:
Calculator.h: הגדירו את המחלקהCalculator, שמבצעת פעולות מתמטיות עם טיפול מתאים בשגיאות.במחלקה
Calculatorצריכה להיות מתודהdivide(double numerator, double denominator)שמחזירה את תוצאת החילוק. עם זאת, אם המכנה הוא אפס, עליה לזרוקstd::runtime_errorעם ההודעהDivision by zero. אם המונה או המכנה שליליים, עליה לזרוקstd::invalid_argumentעם ההודעהNegative values not allowed.הוסיפו גם מתודה
safeSqrt(double value)שמחזירה את השורש הריבועי של הערך. אם הערך שלילי, זרקוstd::runtime_errorעם ההודעהCannot compute square root of negative number. תצטרכו לכלול את<cmath>בשביל הפונקציהsqrt.Calculator.cpp: ממשו את המתודות שהוצהרו בקובץ הכותרת. הקפידו לכלול את קובצי הכותרת הדרושים (<stdexcept>ו-<cmath>) ולבדוק את התנאים לפני ביצוע הפעולות.main.cpp: קראו שלוש קלטים (כל אחד בשורה נפרדת):- סוג הפעולה:
divideאוsqrt - המספר הראשון (double)
- המספר השני (double): משמש רק לחלוקה, ומתעלמים ממנו עבור sqrt
צרו אובייקט
Calculatorובצעו את הפעולה המבוקשת בתוך בלוק try. אם הפעולה מצליחה, הדפיסוResult: [value]. אם נתפסת חריגה, הדפיסוError: [message], כאשר ההודעה מתקבלת מהמתודהwhat()של החריגה.אחרי בלוק ה-try-catch (בלי קשר לשאלה אם התרחשה חריגה), הדפיסו
Calculation completeכדי להדגים שהתוכנית ממשיכה לפעול לאחר טיפול בחריגות.- סוג הפעולה:
לדוגמה, עם הקלטים divide, 10 ו-2:
Result: 5
Calculation completeעם הקלטים divide, 10 ו-0:
Error: Division by zero
Calculation completeעם הקלטים divide, -5 ו-2:
Error: Negative values not allowed
Calculation completeעם הקלטים sqrt, 16 ו-0:
Result: 4
Calculation completeעם הקלטים sqrt, -9 ו-0:
Error: Cannot compute square root of negative number
Calculation completeשימו לב כיצד התוכנית מטפלת בשגיאות בצורה מסודרת באמצעות תפיסת חריגות והצגת הודעות משמעותיות, ואז ממשיכה לפעול. תבנית זו שומרת על טיפול נקי בשגיאות ומאפשרת ללוגיקה הראשית להתמקד במסלול התקין.
נסו בעצמכם
#include <iostream>
#include <string>
#include "Calculator.h"
using namespace std;
int main() {
// קראו את הקלטים
string operation;
double num1, num2;
cin >> operation;
cin >> num1;
cin >> num2;
// צרו אובייקט מסוג Calculator
Calculator calc;
// TODO: השתמשו בבלוק try-catch כדי לבצע את הפעולה
// - אם operation הוא "divide", קראו ל־calc.divide(num1, num2)
// - אם operation הוא "sqrt", קראו ל־calc.safeSqrt(num1)
// - אם הפעולה הצליחה, הדפיסו "Result: " ואחריו את התוצאה
// - תפסו חריגות והדפיסו "Error: " ואחריו את הודעת החריגה (השתמשו ב־what())
// - אחרי ה־try-catch, הדפיסו תמיד "Calculation complete"
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות11מושגי OOP מתקדמים
הרכבה לעומת הורשהMixins באמצעות CRTPאידיום Pimplמחיקת טיפוסיםמחלקות Enum וטיפוסיות חזקהטיפול בחריגות ב-OOPהיררכיות חריגות מותאמות אישית3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין