480 likes | 691 Views
תרגול מס' 10. המחלקה String עבודה עם קבצים תבניות חריגות. דוגמה - String. שימוש בבנאים, הורסים והעמסת אופרטורים. דוגמה - המחלקה String. השימוש במחלקות, בנאים, הורסים והעמסת אופרטורים מאפשר לנו להגדיר מחלקה עבור String כך שייחסכו מאיתנו החסרונות של השימוש ב- char*
E N D
תרגול מס' 10 המחלקה String עבודה עם קבצים תבניות חריגות
דוגמה - String שימוש בבנאים, הורסים והעמסת אופרטורים מבוא לתכנות מערכות - 234122
דוגמה - המחלקה String • השימוש במחלקות, בנאים, הורסים והעמסת אופרטורים מאפשר לנו להגדיר מחלקה עבור String כך שייחסכו מאיתנו החסרונות של השימוש ב-char* • בזכות בנאים והורסים לא נצטרך לנהל את הזיכרון ידנית • בזכות העמסת אופרטורים נוכל לשמור על כל הנוחות של char*, למשל גישה כמערך • בזכות העמסת אופרטורים נוכל לאפשר פעולות בסיסיות בצורה נוחה - למשל שרשור מבוא לתכנות מערכות - 234122
String • classString { • intlength; • char* data; • staticchar* allocate_and_copy(constchar* data, int size); • voidverify_index(int index) const; • public: • String(constchar* str = ""); // String s1; or String s1 = "aa"; • String(constString& str); // String s2(s1); • ~String(); • intsize() const; • String& operator=(constString&); // s1 = s2; מבוא לתכנות מערכות - 234122
String • String& operator+=(constString& str); // s1 += s2; • constchar& operator[](int index) const; // c = s1[5] • char& operator[](int index); // s1[5] = 'a' • friendostream& operator<<(ostream&,constString&); // cout << s1; • friendbooloperator==(constString&, constString&); // s1==s2 • friendbooloperator<(constString&, constString&); // s1<s2 • }; • booloperator!=(constString& str1, constString& str2); • booloperator<=(constString& str1, constString& str2); • booloperator>(constString& str1, constString& str2); • booloperator>=(constString& str1, constString& str2); • constStringoperator+(constString& str1, constString& str2); מבוא לתכנות מערכות - 234122
מימוש String • voiderror(constchar* str) { • cerr << "Error: " << str << endl; • exit(0); • } • char* String::allocate_and_copy(constchar* str, int size) { • returnstrcpy(newchar[size + 1], str); • } בהמשך נחליף את error בשימוש בחריגות פונקצית עזר סטטית,אין לנו כאן צורך ב-this מבוא לתכנות מערכות - 234122
מימוש String • String::String(constchar* str) : • length(strlen(str)), • data(allocate_and_copy(str, length)) { • } • String::String(constString& str) : • length(str.size()), • data(allocate_and_copy(str.data, length)) { • } • String::~String() { • delete[] data; • } • intString::size() const { • returnlength; • } מבוא לתכנות מערכות - 234122
מימוש String • String& String::operator=(constString& str) { • if (this == &str) { • return *this; • } • delete[] data; • data = allocate_and_copy(str.data, str.size()); • length = str.length; • return *this; • }String& String::operator+=(constString& str) { • char* new_data = allocate_and_copy(data, str.size() + size()); • strcat(new_data, str.data); • delete[] data; • length += str.length; • data = new_data; • return *this; • } מבוא לתכנות מערכות - 234122
מימוש String • voidString::verify_index(int index) const { • if (index >= size() || index < 0) { • error("Bad index"); • } • return; • } • constchar& String::operator[](int index) const { • verify_index(index); • returndata[index]; • } • char& String::operator[](int index) { • verify_index(index); • returndata[index]; • } פונקציות המחזירות &צריכות בדרך כלל שתי גרסאות - עבור עצמים רגילים ועבור קבועים מבוא לתכנות מערכות - 234122
מימוש String • booloperator==(constString& str1, constString& str2) { • returnstrcmp(str1.data, str2.data) == 0; • } • ostream& operator<<(ostream& os, constString& str) { • returnos << str.data; • } • booloperator<(constString& str1, constString& str2) { • returnstrcmp(str1.data, str2.data) < 0; • } מבוא לתכנות מערכות - 234122
מימוש String • booloperator!=(constString& str1, constString& str2) { • return !(str1 == str2); • } • booloperator<=(constString& str1, constString& str2) { • return !(str2 < str1); • } • booloperator>(constString& str1, constString& str2) { • return str2 < str1; • } • booloperator>=(constString& str1, constString& str2) { • return str2 <= str1; • } • constStringoperator+(constString& str1, constString& str2) { • return String(str1) += str2; • } מבוא לתכנות מערכות - 234122
דוגמה - קוד המשתמש ב-String intmain(intargc, char **argv) { String s = "So long"; String s2 = "and thanks for all the fish."; String sum = s+ " " + s2; sum[sum.size() - 1] = '!'; cout << sum << endl; return 0; } כל האפשרויות הקיימות עבור char* נתמכות במחלקה החדשה רק כעת אין צורך בניהול זיכרון מפורש מבוא לתכנות מערכות - 234122
std::string • קובץ ה-headerstringמהספריה הסטנדרטית של C++ מכיל מימוש של המחלקה std::stringאשר תומכת בכל הפעולות שמימשנו כאן ועוד • הקפידו להשתמש ב-std::string ולא ב-char*עבור מחרוזות ב-++C מבוא לתכנות מערכות - 234122
עבודה עם קבצים המחלקות ofstream ו-ifstream מבוא לתכנות מערכות - 234122
קלט/פלט עם קבצים ב-C++ • כמו ב-C גם ב-C++ הטיפול בקבצים דומה לטיפול בערוצי הקלט/פלט הסטנדרטיים • ב-C++ עבודה עם קבצים מתבצעת בעזרת מחלקות המוגדרות בקובץ fstream • המחלקה ofstreamמשמשת לכתיבה לקובץ • המחלקה ifstreamמשמשת לקריאה מקובץ • לשתי המחלקות יש בנאי המקבל את שם הקובץ לפתיחה • לשתי המחלקות יש הורס המוודא שהקובץ נסגר כאשר העצם נהרס • הדפסהוקריאה מתבצעות על ידי שימוש ב->> ו-<< כמו עבור ערוצים רגילים • שאר הפעולות הדרושות על ערוצי הפלט מבוצעות כמתודות של המחלקות • למשל המתודה eof מחזירה חיווי האם הגענו לסוף הקובץ מבוא לתכנות מערכות - 234122
קלט/פלט עם קבצים - דוגמה • #include<fstream>usingstd::ifstream;usingstd::ofstream;usingstd::cerr;usingstd::endl;voidcopyFile(constchar* fromName, constchar* toName) { • ifstream from(fromName); • if (!from) { • cerr << "cannot open file " << fromName << endl; • return; } • ofstream to(toName); • if (!to) { • cerr << "cannot open file " << toName << endl; • return; } • while (!from.eof()) { • char c; • from >> c; • to << c;}} המרה ל-bool למה לא צריך לסגור את הקובץ? מבוא לתכנות מערכות - 234122
תבניות תבניות של פונקציות תבניות של מחלקות מבוא לתכנות מערכות - 234122
תכנות גנרי • נניח שברצוננו לכתוב פונקציה למציאת המקסימום במערך של עצמים • לכל טיפוס נצטרך לרשום מחדש פונקציה כמעט זהה • קל לנו לראות את התבנית הכללית של פונקציות המוצאות מקסימום במערך • רק שם הטיפוס משתנה (בדוגמאות שלנו int ו-string) • ברצוננו לכתוב קוד גנרי שיתאים לכל טיפוס • כיצד עשינו זאת ב-C? מה היו החסרונות בשיטה זו? intmax(constint* array, int size) { int result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i]; } } return result; } stringmax(conststring* array, int size) { string result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i]; } } return result; } מבוא לתכנות מערכות - 234122
תבניות • ניתן לכתוב תבניות של קוד - קוד התלוי בפרמטרים של זמן הקומפילציה • כדי להגדיר תבנית לפונקציה משתמשים במילה השמורה templateלפני הגדרת הפונקציה ומכריזים בתוך < > על הפרמטרים של התבנית: • הקומפיילר ישתמש בתבנית כדי ליצור קוד במקרה הצורך • תהליך יצירת מופע של הקוד המוגדר על ידי התבנית מתבצע בזמן הקומפילציה וקרוי instantiation • תבניות יש להגדיר תמיד בקבצי h כך שיהיו זמינות לקומפיילר template<classT> Tmax(constT* array, int size) { T result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i];}}return result;} ניתן להשתמש במילה השמורה typename במקום ב-class בתוך קוד התבנית T מייצג שם של טיפוס וניתן להשתמש בו כמו בשם של טיפוס רגיל: להכריז על משתנים מסוג T ולבצע עליהם פעולות מבוא לתכנות מערכות - 234122
תבניות - שימוש • כדי להשתמש בפונקציה המוגדרת על ידי תבנית ניתן לקרוא לה לפי שמה ובתוספת הפרמטרים הדרושים לתבנית: • ניתן לקרוא לפונקציה ללא ציון הפרמטרים לתבנית - במקרה זה הקומפיילר יסיק אותם בעצמו • במקרה זה הפונקציה משתתפת בשלב פתרון ההעמסה יחד עם פונקציות נוספות intarray[] = {4, -1 ,2}; stringarray2[] = {"Hello", "cruel", "world"}; cout<< max<int>(array, 3) << endl; cout<< max<string>(array2, 3) << endl; intarray[] = {4, -1 ,2}; stringarray2[] = {"Hello", "cruel", "world"}; cout<< max(array, 3) << endl; cout<< max(array2, 3) << endl; array הוא int* ולכן תיקרא max<int> array2 הוא string* ולכן תיקרא max<string> מבוא לתכנות מערכות - 234122
תבניות והעמסת פונקציות • בפתרון העמסה אשר מעורבות בו תבניות: • הקומפיילר יעדיף פונקציה אשר מתאימה בדיוק לפרמטרים מאשר שימוש בתבנית (על ידי הסקת טיפוסים) • הקומפיילר יעדיף שימוש בתבנית על שימוש בהמרות • הקומפיילר לא יבצע המרות אוטומטיות כדי להתאים לתבנית • ניתן להכריח שימוש בפונקצית תבנית על ידי ציון הפרמטרים לתבנית במפורש מבוא לתכנות מערכות - 234122
תבניות - העמסת פונקציות • דוגמאות: intmax(int a , intb , int c ) { if (a > b && a > c) return a ; if (b > c) return b ; return c; } // ... int j = max (1,3,3); // char c = max ('w','w','w');// Strings = max (s1,s2,s3); // int j = max (1,'a','a'); // template<classT> Tmax (T a , T b , T c ) { if (a > b && a > c) return a ; if (b > c) return b ; return c; } // ... int j = max(1,3,3); // charc = max ('w','w','w'); // Strings = max (s1,s2,s3); // int j = max (1,'a','a'); // int j = max<int> (1,'a','a'); // מבוא לתכנות מערכות - 234122
תבניות - יצירת מופעים • כאשר הקומפיילר משתמש בתבנית הוא מנסה ליצור פונקציה לפי התבנית על ידי החלפת הפרמטרים לתבניתבערכים שהועברו לה. • האם תהליך זה יכול להיכשל? • בכדי שהשימוש בתבנית יצליח על הארגומנטים המועברים לתבנית לעמוד בדרישות הלא מפורשות על הטיפוס המופיעות בקוד • אילו דרישות יש על הטיפוס T ב-max? template<classT> Tmax(constT* array, int size) { T result = array[0]; for (int i = 1; i < size; ++i) { if (result < array[i]) { result = array[i];} } return result;} Stack array[] = {Stack(50), Stack(60), Stack(70)}; cout<< max(array, 3) << endl; מבוא לתכנות מערכות - 234122
מחלקות גנריות • ניתן להגדיר תבנית עבור מחלקה ובכך לממש מחלקות גנריות בדומה ל-C • בניגוד ל-Cלא נצטרך לשלוח מצביעים לפונקציות והקומפיילר יוודא את נכונות הטיפוסים בעצמו template <classT> classArray { T* data; intsize; public: explicitArray(intsize); Array(constArray& a); ~Array(); Array& operator=(constArray& a); intsize() const; T& operator[](int index); constT& operator[](int index) const; }; בתוך ההכרזה על המחלקה ניתן לרשום Array במקום Array<T> מבוא לתכנות מערכות - 234122
מחלקות גנריות • בניגוד לפונקציות, עבור מחלקות חייבים לרשום במפורש את הארגומנטים לתבנית - הקומפיילר לא יכול להסיק אותם בעצמו • כל שימוש בתבנית עם ארגומנטים אחרים יוצר טיפוס חדש voidf(Array<string>& words) { Array<int> numbers(100); for(int i = 0; i < numbers.size(); ++i) { numbers[i] = i; } words = numbers; // error, type mismatch } מבוא לתכנות מערכות - 234122
מחסנית גנרית ב-C++ template <classT = int> classStack { T* data; intsize; intnextIndex;public: Stack(int size = 100); Stack(constStack& s); ~Stack(); Stack& operator=(constStack&); voidpush(constT& t); voidpop(); T& top(); constT& top() const; intgetSize()const; }; • נשפר את המחסנית שלנו כך שתהיה גנרית: ניתן לתת ערך ברירת מחדל, כמו בערכי ברירת מחדל של פונקציות מבוא לתכנות מערכות - 234122
מחסנית גנרית ב-C++ template<classT> Stack<T>::Stack(int size) : data(newT[size]), size(size), nextIndex(0) { } template <classT> Stack<T>::Stack(constStack<T>& s) : data(newT[s.size]), size(s.size), nextIndex(s.nextIndex) { for (int i = 0; i < nextIndex; ++i) { data[i] = s.data[i]; } } template <classT> Stack<T>::~Stack() { delete[] data; } מבוא לתכנות מערכות - 234122
מחסנית גנרית ב-C++ template<classT> Stack<T>& Stack<T>::operator=(constStack<T>& s) { if (this == &s) { return *this; } delete[] data; data = newT[s.size]; size = s.size; nextIndex = s.nextIndex; for (int i = 0; i < nextIndex; ++i) { data[i] = s.data[i]; } return *this; } מבוא לתכנות מערכות - 234122
מחסנית גנרית ב-C++ template<classT> voidStack<T>::push(constT& t) { if (nextIndex >= size) { error("Stack full"); } data[nextIndex++] = t; } template <classT> voidStack<T>::pop() { if (nextIndex <= 0) { error("Stack empty"); } nextIndex--; } מבוא לתכנות מערכות - 234122
מחסנית גנרית ב-C++ template<classT> T& Stack<T>::top() { if (nextIndex <= 0) { error("Stack empty");} returndata[nextIndex - 1]; } template <classT> constT& Stack<T>::top() const{ if (nextIndex <= 0) { error("Stack empty"); } returndata[nextIndex - 1]; } template <classT> intStack<T>::getSize()const{ returnsize; } מבוא לתכנות מערכות - 234122
שימוש במחסנית הגנרית intmain() { Stack<int> s; Stack<string> s2; s.push(20); s2.push("Hello"); Stacks3(s); s3 = s2; // error cout << s.top() << s2.top() << endl; return 0; } למה מתקבלת שגיאה? מבוא לתכנות מערכות - 234122
תבניות - פרמטרים • ניתן להגדיר תבניות המקבלותיותר מפרמטר אחד template<classT, classS> structPair { Tfirst; Ssecond; Pair(constT& t, constS& s) : first(t), second(s) {} }; Pair<char, double> p('a', 15.0); cout<< "(" << p.first << "," << p.second << ")"; הרווח הזה הוא חובה List<Pair<string, int> > phonebook; phonebook.add(Pair<string, int>("Dani",8343434)); phonebook.add(Pair<string, int>("Rami",8252525)); מבוא לתכנות מערכות - 234122
תבניות - פרמטרים • ניתן להגדיר תבניות גם עבור פרמטרים מטיפוס int, למשל: template<classT, intN> classVector { T data[N]; public: Vector(); Vector& operator+=(constVector& v); //... }; voidf(Vector<int, 3>& v) { Vector<int, 5> v5; Vector<int, 3> v3; v3 += v; // o.k. v5 += v; // error } N נקבע בזמן הקומפילציה, לכן אנו מגדירים למעשה מערך בגודל קבוע כשדה במחלקה המספר הוא חלק מהטיפוס, לכן Vector<int, 3> ו-Vector<int, 5> הם טיפוסים שונים נסיון לחבר וקטורים ממימדים שונים לא יתקמפל מבוא לתכנות מערכות - 234122
תבניות - שגיאות קומפילציה • כאשר הקומפיילר עובר על תבנית הוא יכול למצוא בה רק שגיאות בסיסיות • כאשר הקומפיילר יוצר מופע של תבנית יתגלו שגיאות התלויות בארגומנט הספציפי • השגיאה המתקבלת מהקומפיילר תהיה מורכבת ממספר שורות ותכלול את רשימת התבניות והארגומנטים שלהן • בגלל השגיאות המסובכות מומלץ לכתוב תחילה קוד רגיל ורק אחרי שהוא עובד להמיר אותו לתבנית voidf() { Stack<Pair<int, int> > ranges(10); } ..\main.cpp: In constructor `Stack<T>::Stack(int) [with T = Pair<int, int>]': ..\main.cpp:241: instantiated from here ..\main.cpp:108: error: no matching function for call to `Pair<int, int>::Pair()' ..\main.cpp:233: note: candidates are: Pair<int, int>::Pair(const Pair<int, int>&) ..\main.cpp:237: note: Pair<T, S>::Pair(T, S) [with T = int, S = int] מבוא לתכנות מערכות - 234122
תבניות - סיכום • ניתן להגדיר תבנית לפונקציה • ניתן להגדיר מחלקות גנריות בעזרת שימוש ב-template • בניגוד ל-C כל בדיקות הטיפוסים נעשות בזמן קומפילציה • כדי שהשימוש בתבנית יתקמפל על הארגומנטים המועברים לתבנית להיות בעלי כל התכונות הדרושות לפי התבנית • ניתן להגדיר תבניות שהפרמטרים שלהן הם מספרים • ניתן להגדיר תבניות עם מספר פרמטרים • מומלץ לכתוב תחילה קוד ללא תבניות ולהמירו אח"כ כדי למנוע התמודדות עם פלט מסובך מהקומפיילר מבוא לתכנות מערכות - 234122
חריגות שימוש ב-try, catch ו-throw הקצאה על ידי אתחול מבוא לתכנות מערכות - 234122
חריגות • מנגנון החריגות עובד בעזרת שלושת המילים השמורות throw, try ו-:catch • throw: מקבלת כפרמטר את העצם אותו יש לזרוק כחריגה • try: מציינת תחילה של בלוק אשר ייתכן ותיזרק ממנו חריגה שנרצה לתפוס • catch: מציינת סוג חריגה לתפוס ואת הקוד המתאים לטיפול בחריגה • כאשר נזרקת חריגה הקוד הרגילמפסיק להתבצע ומתחילה יציאהמכל פונקציה בתכנית עד אשרהחריגה מטופלת • אם חריגה לא מטופלת בכלל(נזרקת מ-main) התכנית תסתיים voidf(int n) { if (n < 0) { throw -1;}} intmain(intargc, char **argv) { try { f(-7); } catch (int e) { cerr << "Error: " << e << endl; }} מבוא לתכנות מערכות - 234122
חריגות • כאשר חריגה נזרקת מתבצעים הדברים הבאים: • כל עוד אנחנו לא בתוך בלוק tryקוראים להורסים של המשתנים המקומיים ויוצאים מהפונקציה • אם אנחנו בבלוק try בודקים האם קיים catch מתאים לסוג החריגה הקיים • אם לא, קוראים להורסים וממשיכים לצאת • אם כן, מתבצע הקוד בבלוק ה-catch ולאחריו הפונקציה ממשיכה מהשורה שאחרי בלוק ה-catch האחרון • ייתכנו מספר בלוקים של catch עבור טיפוסים שונים • תהליך היציאה מפונקציות עד לתפיסת שגיאה נקרא התרת המחסנית(stack unwinding) • בין זריקת החריגה ועד לתפיסתה הקוד היחיד שמתבצע הוא של הורסים של המשתנים המקומיים המשוחררים מבוא לתכנות מערכות - 234122
חריגות voidf(int n) { if (n < 0) { throw -1;}}voidg(int n) { f(-n);}voidh(int n) { g(2*n);}intmain() { try { h(7); } catch (int e) { cerr << "Error: " << e;}} • השימוש בחריגות מונע כתיבה מיותרת של קוד עבור טיפול בשגיאות בכל פונקציה בדרך • מי שיודע למצוא את השגיאה זורק חריגה • רק מי שיודע לטפל בשגיאה מתייחס אליה • בשימוש בקודי שגיאה כמו ב-C הטיפול במקרי השגיאה נהפך למרבית הקוד • ב-C++ ניתן לזרוק כל עצם שהוא אבל נהוג לזרוק רק מחלקות שנוצרו במיוחד כדי לציין שגיאות ספציפיות throw call call call main h g f return return return מבוא לתכנות מערכות - 234122
חריגות template<typenameT> classStack { T * data; intsize; intnextIndex;public: Stack(int size = 100); Stack(constStack& stack); ~Stack(); Stack& operator=(constStack& s); voidpush(constT& t); voidpop(); T& top(); constT& top()const; intgetSize()const; classFull {}; classEmpty {}; }; • נוסיף חריגות למחסנית הגנרית שלנו: template<typenameT> voidStack::push(constT& t) { if (nextIndex >= size) { throw Full(); } data[nextIndex++] = t; } template<typenameT> voidStack::pop() { if (nextIndex <= 0) { throw Empty(); } nextIndex--; } ניתן להגדיר מחלקות בתוך מחלקות מבוא לתכנות מערכות - 234122
חריגות - שימוש במחסנית voidf(Stack<int>& s) { try { s.pop(); for (int i = 0; i < 10; ++i) { s.push(i); } } catch (Stack<int>::Empty& e) { cerr << "No numbers"; } catch (Stack<int>::Full& e) { cerr << "Not enough room"; } catch(...) { cerr << "Oops"; throw; } } • ניתן להשתמש במספר משפטי catch עבור try יחיד • ניתן להשתמש ב-... כדי לתפוס כל חריגה • במקרה זה לא ניתן לגשת לחריגה שנתפסה • אם כמה מקרים מתאימים לתפיסת החריגה ייבחר הראשון שמופיע • לכן ... תמיד מופיע אחרון • ניתן לזרוק חריגה מחדש מ-catch על ידי throwללא פרמטרים מבוא לתכנות מערכות - 234122
חריגות בשביל להגדיר מחלקה מקוננת כך יש להכריז עליה בתוך String classString::BadIndex { public: intindex; BadIndex(int index) : index(index) {}};voidString::verify_index(int index) const { if (index >= size() || index < 0) { throwBadIndex(index);}return;}constchar& String::operator[](int index) const { verify_index(index); returndata[index];}char& String::operator[](int index) { verify_index(index); returndata[index];} • חריגות יכולות להיות יותר מורכבות • למשל ניתן לשלוח בהן מידע מדויק על השגיאה voidf(String& s) { try { cout << s[50]; } catch (String::BadIndex& e) { cerr << "Bad Index: " << e.index;}} מבוא לתכנות מערכות - 234122
חריגות • ניתן לזרוק חריגות מבנאי: • מסוכן לזרוק חריגות מהורס: • יכולה להיות רק חריגה אחת בו זמנית • בזמן זריקת חריגה הורסים ממשיכים להתבצע • אם נזרקת חריגה נוספת התכנית תתרסק • כאשר הקצאת זיכרון על ידי newנכשלתנזרקת חריגה מסוג std::bad_alloc Rational::Rational(intn, intd) : num(num), denom(denom) { if (denom == 0) { throwDivideByZero();}} intmain() { try { while (true) { newint[1000000]; } } catch (std::bad_alloc& e) { cerr << "Out of memory"; } return 0;|} מבוא לתכנות מערכות - 234122
קוד בטוח לחריגות • לחריגות יש חיסרון חשוב - כל קריאה בקוד שלנו עלולה לגרום לזריקת חריגה (בצורה ישירה או עקיפה) ולכן הקוד צריך להיות מוכן לזריקת חריגה בכל שלב • לדוגמה, מה הבעיה באופרטור ההשמה של String שלנו? char* String::allocate_and_copy(constchar* str, int size) { returnstrcpy(newchar[size+1], str);} String& String::operator=(constString& str) { if (this == &str) { return *this; } delete[] data; data = allocate_and_copy(str.data, str.size()); length = str.length; return *this; } מבוא לתכנות מערכות - 234122
קוד בטוח לחריגות • כאשר מבצעים קוד של שחרור והקצאות - קודם מבצעים את החלק הבעייתי ואח"כ את שחרור המידע הישן: • עדיין קיימת בעיה - מה קורה אם יש שתי הקצאות בבת אחת? char* String::allocate_and_copy(constchar* str, int size) { returnstrcpy(newchar[size+1], str);} String& String::operator=(constString& str) { char* temp = allocate_and_copy(str.data, str.size()); delete[] data; data= temp; length = str.length; return *this; } למה לא צריך בדיקת השמה עצמית? מבוא לתכנות מערכות - 234122
הקצאת משאבים על ידי אתחולResource Acquisition is Initialization • כדי להתמודד עם הסכנות הלא צפויות של חריגות נהוג לעטוף את כל הקצאות המשאבים במחלקות • בשיטה זו ההורס של המחלקה אחראי לנקות • הורסים נקראים אוטומטית, בפרט בזמן התרת המחסנית • השימוש בשיטה זו חוסך למתכנת התעסקות מייגעת בהקצאות ושחרורים voidgood() { string str1; string str2; // ... code ... } voidbad() { char* str1 = newchar[N+1]; try { char* str2 = newchar[N+1]; // ... code ... } catch (bad_alloc& e) { delete[] str1; } } מבוא לתכנות מערכות - 234122
חריגות -שימוש נכון • המנעו מכתיבת קוד אשר משתמש בחריגות כבערכי חזרה • המנעו משימוש בחריגות לשם שליטה בקוד • המנעו מלהכריח את המשתמשים בקוד שלכם לעשות זאת voidgood(Stack<int>& s) { while(s.getSize() > 0) { cout << s.top() << endl; s.pop(); } } voidbad(Stack<int>& s) { while(true) { try { cout << s.top() << endl; s.pop(); } catch (Stack<int>::Empty& e) { break; } } } מבוא לתכנות מערכות - 234122
חריגות - סיכום • טיפול בשגיאות מתבצע ב-C++ בעזרת throw, try ו-catch • אפשר לזרוק כל דבר אבל נהוג לזרוק רק עצמים ממחלקות ייעודיות • השתמשו בחריגות כדי לאפשר לבנאי להיכשל • שגיאות מהספריה הסטנדרטית, כמו כשלון של new מטופלות על ידי חריגות • כדי לשמור על קוד בטוח לחריגות עטפו הקצאות משאבים במחלקות • המנעו מכתיבת קוד אשר משתמש בחריגות כבערכי חזרה מבוא לתכנות מערכות - 234122