1 / 32

Object Oriented Programming

Object Oriented Programming. Tirgul 10: Exceptions. למה צריך Exceptions ?. אחד העקרונות של OOP הוא חלוקת האחריות בין האובייקטים השונים, וכן בתוך אובייקט – בין הפונקציות השונות. החלוקה למחלקות תתבצע בד"כ ע"פ חלוקה הגיונית של "כלים" שאנחנו צריכים ע"מ לממש את התוכנית שלנו.

gitano
Download Presentation

Object Oriented Programming

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. Object Oriented Programming Tirgul 10: Exceptions

  2. למה צריך Exceptions? • אחד העקרונות של OOP הוא חלוקת האחריות בין האובייקטים השונים, וכן בתוך אובייקט – בין הפונקציות השונות. • החלוקה למחלקות תתבצע בד"כ ע"פ חלוקה הגיונית של "כלים" שאנחנו צריכים ע"מ לממש את התוכנית שלנו. • בתוך המחלקה, החלוקה לפונקציות תתבצע בד"כ לפי תחומי אחריות, כאשר לכל פונקציה יש את תחום האחריות שעליו היא אחראית (כמובן שבמקרה שניתן לחלק תחום אחריות ראשי אחד למספר תתי תחומים – ניתן לחלק את הפונקציה שלנו למספר פונקציות שיקראו ע"י הפונקציה בעלת תחום האחריות הראשי).

  3. למה צריך Exceptions? • אבל מה קורה כאשר פונקציה אינה מסוגלת לבצע את הפעולה שהיא התבקשה לבצע? • נניח שפונקציה B כלשהי לא מסוגלת לבצע פעולה מסוימת שהתבקשה. תמיד תהיה פונקציה A שביקשה מ-B לבצע את אותה הפעולה (גם ל-main() יש את מערכת ההפעלה).נרצה שכאשר פונקציה B אינה מסוגלת לבצע את מה שהיא התבקשה לבצע – שתודיע על כך לפונקציה A שקראה לה.

  4. למה צריך Exceptions? • הפונקציה A קראה לפונקציה B משום ש-A אחראית על פעולה מסוימת ש-B היא חלק ממנו.אם B אינה מסוגלת לבצע את תפקידה, אזי גם A לא מסוגלת לבצע את תפקידה. • נצפה מ-A לאחד מ-2 דברים: • לנסות לפעול בצורה שונה ע"מ לבצע את הנדרש ממנה. • להתריע לפונקציה שקראה ל-A על כך ש-A אינה מסוגלת לבצע את תפקידה.

  5. למה צריך Exceptions? • פיתרון 1: שימוש בערך מוחזר. • חסרונות: • לא תמיד יש ערך מוחזר פנוי בשביל לציין טעות. • בעייתי לדווח את מהות הטעות. • ל-Ctor ו-Dtor אין ערכים מוחזרים. • מחייב לבדוק בכל פעם את הערך המוחזר ולהוסיף בכל מקום קוד שיפעל בהתאם. • מחייב לדעת בכל נקודה בקוד מה המשמעות של כל ערך שגיאה שחוזר.

  6. למה צריך Exceptions? • מנגנון ה-Exceptions נותן לנו 2 יתרונות עיקריים: • התראה על תקלות בזמן הריצה. • מתן אפשרות לתוכנית להתאוששמהתקלות ולהמשיך לפעול בצורה תקינה.

  7. מנגנון ה-Exceptions • מנגנון ה-Exceptions מורכב מ-3 חלקים: • throw – "זריקת" החריגה ע"י הפונקציה שאינה מסוגלת לבצע את שנדרשה. • try – בפונקציה הקוראת נסמן ב"ניסיון" את החלק שבו עלולה להיות פונקציה שלא תבצע את תפקידה. • catch – הפונקציה הקוראת "תופסת" את עצם החריגה ומטפלת בו בהתאם.

  8. דוגמא 1 int main () } Fraction f1; cin>>f1; Fraction f2(1,1); Fraction f3 = f2/f1; cout<<"f3 is: "<<f3<<endl; } 1st run: input:1/1output: f3 is: 1/1 2nd run: input: 0/1 output: f3 is:1/1 • ניזכר באובייקט Fraction: Fraction::Fraction (int nn, int dd) : m_iNom (nn), m_iDenom (dd) { if (m_iDenom == 0) m_iDenom = 1; reduce (); } constFraction operator / (constFraction &f1, constFraction &f2) { int nn = f1.m_iNom * f2.m_iDenom; int dd = f1.m_iDenom * f2.m_iNom; returnFraction (nn, dd); }

  9. דוגמא 1 constFraction operator / (constFraction &f1, constFraction &f2) { if (f2.m_iNom == 0) throw string("devision by 0"); int nn = f1.m_iNom * f2.m_iDenom; int dd = f1.m_iDenom * f2.m_iNom; returnFraction (nn, dd); } int main () { Fraction f1; cin>>f1; Fraction f2(1,1); try { Fraction f3 = f2/f1; cout<<"f3 is: "<<f3<<endl; } catch (string errorStr) { cerr<<"Error: "<<errorStr<<endl; { } • נרצה שחילוק ב-0 יתריע לנו על תקלה כלשהי: • כיצד ייראה ה-main?

  10. דוגמא 2 //Product.h #ifndef PRODUCT_H #define PRODUCT_H #include <iostream> #include <string> usingnamespace std; classProduct { char m_name[10]; int m_quantity; float m_price; public: Product() {} void init (char* name, int quantity, float price); void print(); }; #endif //Product.cpp #include "Product.h“ voidProduct::init (char* name, int quantity, float price) { if (strlen(name) > 10) throw string("name’s too long"); strcpy(m_name ,name); if (quantity<0 || quantity>100) throw quantity; m_quantity=quantity; if (price<0) throw price; m_price=price; { voidProduct::print() { cout<<m_name<<'\t'<<m_quantity <<'\t'<<m_price<<endl; { • אם יש לנו מספר אפשרויות זריקה, נוכל לאפשר לתפוס אתכולם ע"ירצף של פקודות catch שיתפסו אובייקטים שונים:

  11. int main () { char name[100]; int quantity; float price; Product store[3]; for (int i=0; i<3; i++) { try{ cout << "enter a product number " << i << endl; cin>>name>>quantity>>price; store[i].init(name,quantity,price); { catch (string errStr) { cout << "Error: “ << errStr << endl;; i--; { catch (float f) { cout << "Error: price can't be negative\n"; i--; { catch (int quantity) { if (quantity<0) cout << "Error: quantity must be zero or more\n"; if (quantity>100) cout << "Error: quantity can't be more then 100\n”; i--; { } for (int i=0; i<3; i++) store[i].print(); return 0; } דוגמא 2 enter a product number 0 abcd 10 12.99 enter a product number 1 aaabbbcccddd 10 13.00 Error: name to long enter a product number 1 abcde -12 12 Error: quantity must be 0 or more enter a product number 1 abcde 10 -1.99 Error: price can't be negative enter a product number 1 abcde 10 1.99 enter a product number 2 abcde 101 1.99 Error: quantity can't be more then 100 enter a product number 2 abcde 99 19.99 abcd 10 12.99 abcde 10 1.99 abcde 99 19.99

  12. double Divide (double a, double b) { if (b == 0) throw "Divide by zero"; else return a/b; { void CallDivide() { try { cout<<"5/2="<<Divide(5,2)<<endl; cout<<"5/0="<<Divide(5,0)<<endl; cout<<"7/3="<<Divide(7,3)<<endl; { catch (const char* e) { cout<<"Exception caught at: "; cout<<"CallDivide function"<<endl; //try to fix the exception throw; //re-throw the exception to //the calling function. } { דוגמא 3 – Re-throw int main() { try { CallDivide(); } catch (const char* e) { cout<<"Exception caught at: "; cout<<"main function"<<endl; cout<<e<<endl; } catch( ... ) { cout<<"Unknown Exception!"<<endl; } cout<<"End program"<<endl; return 0; { //OUTPUT: 5/2=2.5 Exception caught at: CallDivide function Exception caught at: main function Divide by zero End program

  13. void f2() { cout<<"f2(): Before throw"<<endl; throw string("some exception"); cout<<"f2(): After throw"<<endl; } void f1() { cout<<"f1(): Before throw"<<endl; f2(); cout<<"f1(): After throw"<<endl; } int main() { try { f1(); { catch (string errStr) { cerr<<"Exception: "<<errStr<<endl; { cout<<"main(): After catch block"<<endl; return 0; } דוגמא 4 • אם אין לנו בלוק catch שיתפוס אתהזריקה – אז היא נזרקת הלאה עד שתיתפס: //OUTPUT: f1(): Before throw f2(): Before throw Exception: some exception main(): After catch block

  14. void f2() throw (string) { cout<<"f2(): Before throw"<<endl; throw string("some exception"); cout<<"f2(): After throw"<<endl; } void f1() { cout<<"f1(): Before throw"<<endl; f2(); cout<<"f1(): After throw"<<endl; } int main() { try { f1(); { catch (string errStr) { cerr<<"Exception: "<<errStr<<endl; { cout<<"main(): After catch block"<<endl; return 0; } הכרזה על חריגות throw (int) { • ניתן להצהיר בזמן ההצהרה עלהפונקציה על כך שהפונקציה שלנו זורקת חריגה. • הכרזות אלו נבדקות בזמן ריצה! • אם תיזרק חריגה שלא הצהרנועליה – התוכנית תסתיים עם הודעת שגיאה. (לא עובד ב-VS!) //OUTPUT: f1(): Before throw f2(): Before throw terminate called after throwing an instance of 'std::string' Abort (core dumped)

  15. ///////////////////////////////// // Exceptions Handling Classes ///////////////////////////////// classBaseException { public: virtualvoid print()=0; }; classderrivedException1 : publicBaseException { public: void print() {cout<<"derrivedException1\n";} }; classderrivedException2 : publicderrivedException1 { public: void print() {cout<<"derrivedException2\n";} }; classderrivedException3 : publicBaseException { public: void print() {cout<<"derrivedException3\n";} }; void f1(){ throw (derrivedException2()); }; היררכיית חריגות int main(){ try { f1(); { catch (BaseException ex) { ex.print(); { catch (derrivedException1 ex) { ex.print(); { catch (derrivedException2 ex) { ex.print(); { catch (derrivedException3 ex) { ex.print(); { return 0; { //OUTPUT: error: cannot allocate an object of abstract type 'BaseException‘ because the following virtual functions are pure within 'BaseException': virtual void BaseException::print()

  16. ///////////////////////////////// // Exceptions Handling Classes ///////////////////////////////// classBaseException { public: virtualvoid print()=0; }; classderrivedException1 : publicBaseException { public: void print() {cout<<"derrivedException1\n";} }; classderrivedException2 : publicderrivedException1 { public: void print() {cout<<"derrivedException2\n";} }; classderrivedException3 : publicBaseException { public: void print() {cout<<"derrivedException3\n";} }; void f1(){ throw (derrivedException2()); }; היררכיית חריגות int main(){ try { f1(); { catch (derrivedException1 ex) { ex.print(); { catch (derrivedException2 ex) { ex.print(); { catch (derrivedException3 ex) { ex.print(); { return 0; { //OUTPUT: derrivedException1 הקומפיילר נותן על כך אזהרה! שימו לב לא לקבל אזהרות כאלה בתרגיל!

  17. ///////////////////////////////// // Exceptions Handling Classes ///////////////////////////////// classBaseException { public: virtualvoid print()=0; }; classderrivedException1 : publicBaseException { public: void print() {cout<<"derrivedException1\n";} }; classderrivedException2 : publicderrivedException1 { public: void print() {cout<<"derrivedException2\n";} }; classderrivedException3 : publicBaseException { public: void print() {cout<<"derrivedException3\n";} }; void f1(){ throw (derrivedException2()); }; היררכיית חריגות int main(){ try { f1(); { catch (derrivedException2 ex) { ex.print(); { catch (derrivedException1 ex) { ex.print(); { catch (derrivedException3 ex) { ex.print(); { return 0; { //OUTPUT: derrivedException2

  18. ///////////////////////////////// // Exceptions Handling Classes ///////////////////////////////// classBaseException { public: virtualvoid print()=0; }; classderrivedException1 : publicBaseException { public: void print() {cout<<"derrivedException1\n";} }; classderrivedException2 : publicderrivedException1 { public: void print() {cout<<"derrivedException2\n";} }; classderrivedException3 : publicBaseException { public: void print() {cout<<"derrivedException3\n";} }; void f1(){ throw (derrivedException3()); }; היררכיית חריגות void f2(){ try { f1(); { catch (derrivedException1 d_ex1) { cout<<"catch f2"<<endl; throw (d_ex1); { { int main(){ try { f2(); { catch (BaseException& ex) { cout<<"catch main\n"; ex.print(); { return 0; { //OUTPUT: catch main derrivedException3

  19. ///////////////////////////////// // Exceptions Handling Classes ///////////////////////////////// classBaseException { public: virtualvoid print()=0; }; classderrivedException1 : publicBaseException { public: void print() {cout<<"derrivedException1\n";} }; classderrivedException2 : publicderrivedException1 { public: void print() {cout<<"derrivedException2\n";} }; classderrivedException3 : publicBaseException { public: void print() {cout<<"derrivedException3\n";} }; void f1(){ throw (derrivedException2()); }; היררכיית חריגות void f2(){ try { f1(); { catch (derrivedException1 d_ex1) { cout<<"catch f2"<<endl; throw (d_ex1); { { int main(){ try { f2(); { catch (BaseException& ex) { cout<<"catch main\n"; ex.print(); { return 0; { //OUTPUT: catch f2 catch main derrivedException1

  20. היררכית חריגות • ישנה היררכית חריגות הקיימת במערכת עצמה. • האב של היררכיה זו הוא האובייקט exception.אובייקט זה מכיל פונקציה וירטואלית בשם what() המחזירה מחרוזת המתארת את החריגה. • רוב אובייקטי השגיאה נמצאים בקבצי הספרייה <stdexcept> ו-<exception>.

  21. היררכיית חריגות - דוגמא //Fraction.cpp classDivideByZeroException : publicruntime_error { public: DivideByZeroException() : runtime_error( "attempted to divide by zero" ) {} }; constFractionoperator / (constFraction &f1, constFraction &f2) { if (f2.m_iNom == 0) throw (DivideByZeroException()); int nn = f1.m_iNom * f2.m_iDenom; int dd = f1.m_iDenom * f2.m_iNom; returnFraction (nn, dd); } istream& operator >> (istream &is, Fraction &frac) { char divSign; is >> frac.m_iNom >> divSign >> frac.m_iDenom; if (frac.m_iDenom == 0) throw (DivideByZeroException()); frac.reduce (); return is; } • נחזור שוב לאובייקט Fraction.ביצענו תיקון באופרטור החילוק,אך אופרטור הקלטעדייןמאפשר לקלוטמכנה 0... int main(){ Fraction f1; bool fracOK = false; while (!fracOK){ try { cin>>f1; fracOK=true; { catch (runtime_error& err) { cout<<err.what()<<endl; } { cout<<f1; return 0; } //INPUT:// OUTPUT: 1/0 attempted to divide by zero 2/0 attempted to divide by zero 2/2 1/1

  22. היררכיית חריגות - דוגמא • ישנו עוד מקום אחד חשוב מאוד שעלינו להוסיף בו חריגה... • ה-Constractor! • נשים לב שמכיוון שבנאי אינו יכול להחזיר ערך – הדרך היחידה להודיע על אי הצלחה בבנייה של אובייקט היא ע"י זריקת חריגה. //Fraction.cpp classDivideByZeroException : publicruntime_error { public: DivideByZeroException() : runtime_error( "attempted to divide by zero" ) {} }; Fraction::Fraction (int nn, int dd) : m_iNom (nn), m_iDenom (dd) { if (m_iDenom == 0) throw (DivideByZeroException()); reduce (); }

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

  24. מימוש חריגות ע"י המהדר– זריקה מ-Ctor classA{ public: A(){cout<<"Ctor A()"<<endl;} ~A(){cout<<"Dtor A()"<<endl;} }; classB : publicA{ public: B(){cout<<"Ctor B()"<<endl; throw 1;} ~B(){cout<<"Dtor B()"<<endl;} }; int main(){ try {B b;} catch (int i){}; return 0; { • כאשר אנחנו זורקים חריגהמה-Ctor של בן, יופעלוה-Dtor-ים של ההורים: • נשים לב שאם הייתה לנוזריקת חריגה ב-Dtor של A – אזי יהיו לנו 2 חריגות"באוויר" ולכן אסור לנו לזרוקחריגות מתוך ה-Dtor.אם נקרא מה-Dtor לפונקציה שעלולהלזרוק חריגה – עלינו להקיף אותהבבלוקי try, catch מתאימים ע"מ שלא תיזרק מאיתנו! • אבל מה קורה עם משתנים שהוקצו דינאמית? // OUTPUT: Ctor A() Ctor B() Dtor A()

  25. #include <iostream> #include <stdexcept> usingnamespace std; void f2(){ string s2("local string"); char *p2 = new char[256]; //… throw runtime_error("some error"); delete[] p2; { void f1(){ string s1("local string"); string* ps = new string("dynamic string"); f2(); delete ps; { int main(){ try {f1();} catch (runtime_error err){ cerr<<"Ex: "<<err.what()<<endl; { return 0; } מימוש חריגות ע"י המהדר Heap string(“dynamic string”) string (“local string”) char* p2 f2(): string (“local string”) char* ps f1(): main(): כאשר נזרקת החריגה – אף אחד לא משחרר את המשתנים שהקצנו דינאמית.

  26. מימוש חריגות ע"י המהדר • הפיתרון: STL + auto_ptr<T>. • ראשית, כאשר אנחנו צריכים container – נשתמש באובייקטי STL שונים. אובייקטים אלה יכולים להיות משתנים מקומיים של הפונקציה ולכן ישוחררו בזמן זריקת החריגה. • כעת, בזמן זריקת החריגה p2 הוא משתנה מקומי ולכן ישתחרר בזמן פירוק מחסנית הקריאות. void f2(){ string s2("local string"); char *p2 = new char[256];//... throw runtime_error("some error"); delete[] p2; { void f2(){ string s2("local string"); vector<char> p2(256); //... throw runtime_error("some error"); {

  27. מימוש חריגות ע"י המהדר void f1(){ string s1("local string"); string* ps = new string("dynamic string"); f2(); delete ps; { void f1(){ string s1("local string"); auto_ptr<string>ps(new String(“dynamic string”); f2(); { • הפיתרון: STL + auto_ptr<T>. • כאשר נרצה להקצות משתנה יחיד נשתמש ב-auto_ptr. • הרעיון שעומד מאחורי auto_ptr הוא זה: • מצביע זה יהיה אובייקט של הפונקציה.לכן יופעל ה-Dtor שלו כאשר תיזרקחריגה והוא יפעיל את ה-Dtor שלהאובייקט שהוא מצביע עליו. template <class T> classauto_ptr { T* ptr; public: explicit auto_ptr(T* p = 0) : ptr(p) {} ~auto_ptr() {delete ptr;} T& operator*() {return *ptr;} T* operator->() {return ptr;} // ...};

  28. מימוש חריגות ע"י המהדר template <class T>auto_ptr<T>& auto_ptr<T>::operator=(auto_ptr<T>& rhs) { if (this != &rhs) { delete ptr; ptr = rhs.ptr; rhs.ptr = NULL; } return *this; } • auto_ptr הוא גרסא מאוד מוגבלת של "מצביע חכם". • המגבלה שלו מתבטאת בעיקר באופי ה"הרסני" של פעולות ההעתקה שלו. • שימו לב שפעולת ההעתקה "מחקה" את המצביע הקודם ועכשיו הוא מצביע על NULL. • דבר דומה קורה גם ב-C.Ctor! • לכן, כאשר אתם רוצים להעביר משתנה auto_ptr לפונקציה – זכרו לעשות זאת by ref! (אחרת תאבדו את המצביע המקורי!)

  29. זריקת חריגה מה-Ctor • בעיות דומות קורות לנו גם כאשר נזרקים חריגות מה-Ctor.גם שם, בהחלט יכול להיות שנאתחל כל מיני משתנים בצורה דינאמית לצורך שימוש בהם, ובהחלט יכול להיות שאחת הפעולות ב-Ctor (או פונקציה כלשהי שנפעיל ממנו) תזרוק חריגה.גם במקרה זה – הקצאות דינאמיות שהוקצו עד לנקודה זו לא ישוחררו. • לכן, גם כאן נעדיף שימוש במצביעים "חכמים" וב-STL.

  30. זריקת חריגה מרשימת אתחול #include <iostream> #include <stdexcept> #include <vector> using namespace std; class X{ vector <char> v1; vector <int> v2; public: X (long s1, int s2); }; X::X(long s1, int s2) try : v1(s1), v2(s2) //initialization list {} // constructor body catch (exception ex){ cerr << "Ex: “ << ex.what() << endl; } int main(){ X(99999999999,3); return 0; } • מה נעשה כאשר נזרקת לנו חריגה מתוך Ctor של אובייקט ברשימת האתחול? // OUTPUT: Ex: St9exception terminate called after throwing an instance of 'std::bad_alloc' what(): St9bad_alloc Abort (core dumped)

  31. זריקת חריגה מאופרטור new #include <iostream> #include <stdexcept> #include <vector> using namespace std; void f(){ char* pc = new char[999999999999]; //... } int main(){ try{ f(); } catch (bad_alloc& ex){ cerr << "Ex: “ << ex.what() << endl; } return 0; } // OUTPUT: Ex: St9bad_alloc #include <iostream> #include <stdexcept> #include <vector> using namespace std; void f(){ char *pc = new (nothrow) char[9999999999999]; if (pc == NULL) cout << "pc2=NULL“ << endl; //... } int main(){ try{ f(); } catch (bad_alloc& ex){ cerr << "Ex: “ << ex.what() << endl; } return 0; } // OUTPUT: pc2=NULL

  32. זריקת חריגה מאופרטור Ctor \ new? • שימו לב שכאשר אנחנו מקצים מערך של אובייקטים אנחנו יכולים לקבל 2 סוגי שגיאות... • שגיאת bad_alloc מהאופרטור new אם לא ניתן להקצות את כמות העצמים שרצינו. • שגיאת runtime_error מכל אחד מהעצמים שהגדרנו. • במקרה כזה – נצרך להוסיף בלוקי catch מתאימים עבור סוגי השגיאה השונים. • שימו לב כמה חשוב לזרוק שגיאה מסוג התואם בצורה אמיתית את סוג השגיאה שקרתה (!) אחרת לא נדע מהם סוגי השגיאות שנצטרך להתמודד איתם.

More Related