חזור לתיקייה: 02340124 - מבוא לתכנות מערכות

3 - טיפול בשגיאות

חיפוש בתיקיה
Ctrl K
לחץ כאן או הקש Ctrl K כדי לחפש בכל התיקיה
  • כללי
  • זריקת שגיאות
  • המימוש
  • מה זורקים?

3 - טיפול בשגיאות

02340124

מבוא לתכנות מערכות

רשימות הרצאה לקריאה בשבת

טיפול בשגיאות

הופק מתוך תקיית התואר של שקד

uni-folder.vercel.app

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

דוגמה (דרמטית)

נניח והטייס במטוס הכניס ערך לא תקין לאחד ממערכות המטוס - האם אנחנו רוצים שהמטוס יקרוס ישר?

עוד דרך לטיפול בשגיאות היא להחזיר ערך שלא ניתן להחזיר.

דוגמה

בפונקציה int factorial(int n) ניתן להחזיר −1-1−1 אם ישנה שגיאה.

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

אופציה נוספת היא להחזיר איזשהו StatusStatusStatus ובנוסף לקבל out  parameterout\; parameteroutparameter.
זה חוסם דברים כמו שרשור פונקציות(נניח שורש של שורש) וגם לא ממש טוב כי כל פונקציה צריכה לבדוק את הסטטוס הזה.
זה די מזעזע וככה עושים את זה ב-CCC.

ישנן שלוש בעיות עיקריות בשיטה זו:

  1. יש סיכוי שנשכח את זה ונתעלם מהשגיאה
  2. כשאנו מגלים שגיאה אנו רוצים לטפל בה הכי מהר שאפשר ופה אנו צריכים לחכות שפונקציות יחזירו לפונקציות אחרות
  3. משרה תלות של פונקציות בפונקציות אחרות לא קשורות.

איך פותרים זאת?

זריקת שגיאות

אנו נזרוק שגיאה מהפונקציה שלנו אשר מגלה את השגיאה ובפונקציה אחרת, אשר יודעת לטפל בשגיאה, תתפוס את השגיאה.
כך, פונקציות שלא מתעסקות עם שגיאות לא יצטרכו להתעסק עם השגיאות.
​3 - טיפול בשגיאות 2026-06-14 14.36.55.excalidraw.svg

אם אף אחד לא תופס את השגיאה, התוכנית תתרסק וזה באסה ואסור שיקרה.

המימוש

ישנן שתי מילים חשובות:

  1. נזרוק שגיאה באמצעות throwthrowthrow
  2. נתפוס שגיאה באמצעות בלוק try−catchtry-catchtry−catch

ונעשה זאת כך:

void printGradesFile(string filename)
{
	ifstream infile(filename);
	if (!infile)
		throw string("cannot open file");
	
	int grade;
	while (infile >> grade)
		cout << grade << endl;
}

int main()
{
	try
	{
		printGradesFile("grades.txt");
	}
	catch (string s)
	{
		cerr << s << endl;
	}
	
	return 0;
}

כשאנו זורקים שגיאה הפונקציה:

  • עוצרת במיידית
  • לא מחזירה ערך

ניתן לתפוס מספר שגיאות בשתי צורות:

דוגמה

int main()
{
	int a, b;
	cin >> a >> b;
	try
	{
		cout << choose(a, 4) << endl;
		cout << choose(b, 4) << endl;
	}
	catch (int n)
	{
		cerr << "error no. " << n << endl;
	}
	return 0;
}
דוגמה

int main()
{
	int a;
	cin >> a;
	try
	{
		cout << choose(a, 4);
	}
	catch (int n)
	{
		cerr << "error no. " << n;
	}
	catch (double f)
	{
		cerr << "other error";
	}
	catch (...)
	{
		cerr << "unknown error";
	}
	
	return 0;
}

ההבדל בין התפיסות הוא בסוג המשתנה הנזרק בלבד.

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

מה זורקים?

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

דוגמה

class DivisonByZero {};

Rational& Rational::operator/=(const Rational& r)
{
	if (r.numerator == 0)
		throw DivisionByZero();
	return *this *= Rational(r.denominator, r.numerator);
}

int main()
{
	Rational r1(1, 2), r2(0, 1);
	try 
	{
		r1 /= r2;
		cout << r1;
	}
	catch (DivisionByZero )
	{
		cerr << "oops";
	}
}

ניתן להגיר כל מני סוגי שגיאות וניתן להעביר ערכים שיגידו לנו דברים על שגיאות.

דוגמה

class OutOfRange
{
	int* data;
	int size;
	
public:
	// ... more methods ...
	int size() const;
	int& operator[](int index);
};

int OutOfRange::getIndex() const
{
	return index;
}

OutOfRange::OutOfRange(int index) : index(index) {}

int& Array::operator[](int index)
{
	if (index < 0 || index >= size)
	{
		throw OutOfRange(index);
	}
	return data[index];
}
הערה

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

דוגמה

class Map
{
public:
	class City;
	class Road;
	
	Map();
	~Map();
	// ...
	
private:
	City* cities;
	Road* roads;
	// ...
};

Map::Map()
{
	cities = new City[INIT_CITY_NUM];
	try
	{
		roads = new Road[INIT_ROAD_NUM];
	}
	catch (const bad_alloc&)
	{
		delete[] cities;
		throw; // throws what we cought
	}
	// ...
}

Map::~Map()
{
	delete[] cities;
	delete[] roads;
}

המהדר עצמו מדי פעם שולח שגיאות. מקרים כמו newnewnew הם חגיגה של שגיאות(ולפחות אנו לא צריכים לבדוק כל פעם כמו ב-CCC)

Set::Set() : maxSize(INITIAL_SIZE), size(0), data(new int[INITIAL_SIZE]) {}
משפט

אם אנו בתוך בלוק catchcatchcatch ואנו זורקים throwthrowthrow בלי כלום, הוא זורק את השגיאה שקיבלנו.

catch (const bad_alloc&)
{
	throw; // throws bad_alloc
}
לא אפשרי מצב בו נזרקו שתי שגיאות אחת אחרי השנייה כל עוד לא טופלה הראשונה\LARGE \color{red} \R{\text{לא אפשרי מצב בו נזרקו שתי שגיאות אחת אחרי השנייה כל עוד לא טופלה הראשונה}}לא אפשרי מצב בו נזרקו שתי שגיאות אחת אחרי השנייה כל עוד לא טופלה הראשונה

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

הערה

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

⇐ כשאין לנו מה לעשות ב-catchcatchcatch משהו שבור בעיצוב התוכנה.