1 / 42

תרגול מס' 12

תרגול מס' 12. ספרית התבניות הסטנדרטית ( STL ) כתיבת אלגוריתמים גנריים מצביעים חכמים. ספרית התבניות הסטנדרטית ( STL ). vector list iterator. ספרית התבניות הסטנדרטית. קיימת בכל מימוש של ++ C מכילה אוספים (Containers) ואלגוריתמים משתמשת בתבניות (templates) : אוספי הנתונים גנריים

harken
Download Presentation

תרגול מס' 12

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. תרגול מס' 12 ספרית התבניות הסטנדרטית (STL) כתיבת אלגוריתמים גנריים מצביעים חכמים

  2. ספרית התבניות הסטנדרטית (STL) vector list iterator מבוא לתכנות מערכות - 234122

  3. ספרית התבניות הסטנדרטית • קיימת בכל מימוש של ++C • מכילה אוספים (Containers)ואלגוריתמים • משתמשת בתבניות (templates): • אוספי הנתונים גנריים • האלגוריתמים המסופקים גנריים • מאפשרת הרחבה ע"י המשתמש • שומרת על יעילות הקוד מבוא לתכנות מערכות - 234122

  4. std::vector • נכיר תחילה את האוסף הפשוט ביותר בספריה – vector. • מערך סדור של איברים • מאפשר גישה לכל איבר באוסף • מאפשר הוספת והסרת איברים • הערה: הקוד בשקפים הוא חלקי וחסרות בו תכונות נוספות ומתקדמות יותר של אוספי ה-STL.דוגמאות הקוד מתרכזות בשימוש בסיסי ופשוט. מבוא לתכנות מערכות - 234122

  5. מתודות נבחרות של vector template<typenameT> classvector { vector(); vector(constvector& c); vector(size_tnum, constT& val = T()); ~vector(); T& operator[](size_t index); constT& operator[](size_t index) const; vectoroperator=(constvector& v); T& at(size_tloc); constT& at(size_tloc) const; voidpop_back(); voidpush_back(constT& val); size_tsize() const; }; • גישה בעזרת אופרטור [] אינה בטוחה • אינה מוודאת את חוקיות האינדקס • גישה בעזרת at() זורקת חריגה מסוג out_of_range עבור אינדקס לא חוקי מבוא לתכנות מערכות - 234122

  6. דוגמאות לשימוש ב-vector intmain() { vector<int> v1; // empty vector vector<int> v2(20, 8); // 20 integers, all of value 8 for(size_t i = 0; i < v2.size(); ++i) { cout << v2[i]; // Unsafe access by index } for(int i = 0; i < 10; ++i) { v1.push_back(i); // Inserts items at the end of the vector } while(v1.size() > 0) { v1.pop_back(); } constvector<int> copy(v1); //Safely copies a vector return 0; } מבוא לתכנות מערכות - 234122

  7. יתרונות • ניהול זיכרון ע"י המחלקה • לא צריך לזכור לשחרר ידנית את המערך • ניתן לשנות את גודל המערך בקלות • ניתן ליצור העתקים ולבצע השמות בקלות • גישה בטוחה • רק כאשר משתמשים ב-at() • כיצד נוכל ליצור וקטור בטוח עבור []? template<typenameT> classSafeVector : publicvector<T> { SafeVector() : vector<T>() {} SafeVector(int s) : vector<T>(s) {} T& operator[](int i) { returnat(i);} constT& operator[](int i) const{ returnat(i);}}; מבוא לתכנות מערכות - 234122

  8. std::list • דומה לוקטור, אך המימוש הוא ברשימה מקושרת • שימוש ברשימה מקושרת מאפשר הוספת איברים מהירה יותר, אך אינו מאפשר גישה מהירה לאמצע האוסף • ההבדל העיקרי בין list ל-vector הוא במימוש, לכן לשני האוספים מנשק דומה • השימוש במנשק דומה מאפשר למשתמש להחליף בקלות את סוג האוסף בשימוש גם בשלבים מאוחרים • כאמור, סיבוכיות היא שיקול משני בד"כ ולכן ניתן להשתמש ב-vector, ורק בהמשך כשיש צורך ברשימה מקושרת ניתן לעבור לשימוש בה מבוא לתכנות מערכות - 234122

  9. מתודות נבחרות של list template<typenameT> classlist { list(); list(constlist& c); list(size_tnum, constT& val = T()); ~list(); listoperator=(constlist& v); voidpop_back(); voidpush_back(constT& val); voidpop_front(); voidpush_front(constT& val); size_tsize() const; }; • נוספו מתודות להסרת והוספת איברים גם בראש הרשימה • מתודות לגישה לאיבר באמצע הרשימה נעלמו • כיצד נוכל לאפשר גישה למשתמש בצורה נוחה? מבוא לתכנות מערכות - 234122

  10. איטרטורים - Iterators • על מנת לאפשר מעבר סדור על איברי אוסף נשתמש באיטרטור • איטרטור יצטרך לאפשר את הפעולות הבאות לפחות: • קידום: שינוי האיטרטור כך שיצביע לאיבר הבא • קריאה: החזרת האיבר המוצבע ע"י האיטרטור • השוואה: בדיקה האם שני איטרטורים מצביעים לאותו איבר • איטרטור אינו מחלקה,אלא מושג (concept) • למשל מצביע הוא איטרטורעבור מערך של C int* array = newint[n]; //... int* i = array; cout<< *i << endl; // Reading the iterator i++; // Advancing the iterator by 1 if(i == array+n) { // Comparing to iterators cout << "End of array!" << endl; } מבוא לתכנות מערכות - 234122

  11. איטרטורים וה-STL • כל האוספים המאפשרים מעבר על איבריהם ב-STL מספקים iterator בעזרתו ניתן לעבור על איבריהם • המתודה begin() מחזירה iterator מתאים לתחילת האוסף • המתודה end() מחזירה iterator מתאים לאיבר "דמה" אחרי האיבר האחרון • למשתמש לא אכפת כיצד ממומש האיטרטור! מבוא לתכנות מערכות - 234122

  12. דוגמאות לשימוש באיטרטורים • הדפסת איברי vector: • הדפסת איברי list: • ניתן לגשת בצורה דומה לכל אוסף ב-STL המאפשר גישה לאיבריו • הטיפוס עבור ה-iterator המתאים מוגדר בתוך כל אוסף בעזרת השם container<int>::iterator for(vector<int>::iterator i = v.begin();i != v.end(); ++i) { cout << *i << endl; } for(list<int>::iterator i = l.begin();i != l.end(); ++i) { cout << *i << endl; } מבוא לתכנות מערכות - 234122

  13. const_iterator • בנוסף כל אוסף מגדיר גם טיפוס const_iterator • כך ניתן להגן על האוסףמפני שינויים כאשר הואמוגדר כ-const • טיפוס האיטרטור המוחזרנקבע בעזרת העמסה עלהמתודות המתאימות vector<int> v; vector<int>::iterator i = v.begin(); *i = 7; // O.K.! *i is int& constvector<int> v; vector<int>::const_iterator i = v.begin(); *i = 7; // error! *i is constint& מבוא לתכנות מערכות - 234122

  14. עוד שימושים • רוב המתודות עבור האוספים משתמשות באיטרטורים כדי לציין מיקומים • הוספת איבר לתוך רשימה ממוינת: • מחיקת האיבר החמישי בוקטור: • שינויים באוסף עלולים לגרום לביטול התוקף של איטרטור (invalidation) • גישה לאיטרטור שאינו בתוקף אינה מוגדרת • למשל vector::insert עלולה לגרום לכל האיטרטורים לצאת מתוקף • מומלץ להניח את ההנחה המחמירה שכל שינוי של אוסף מוציא מתוקף את כל האיטרטורים שלו • אם הנחה זו מחמירה מדי ניתן למצוא מידע מדויק בתיעוד האוסף list<double>::iterator i = myList.begin(); while(i != myList.end() && num < *i) { ++i;} myList.insert(i, num); // Insert num before i myVector.erase(v.begin() + 4); מבוא לתכנות מערכות - 234122

  15. סוגי איטרטורים • אוספים שונים מחזירים איטרטורים שונים • ל-listלמשל, יש bidirectional iterator המאפשר קידום האיטרטור עם אופרטור++ והחזרתו לאחור עם אופרטור-- • ל vector יש random access iteratorהמאפשר גם חיבור מספרים לאיטרטור וחישוב הפרש בין שני איטרטורים (בדומה למצביע) • ל-vector ו-list מס' רב של מתודות ואופרטורים נוספים • כמעט כל פעולה שעולה בדעתכם לבצע כבר ממומשת ב-STL • ניתן פשוט לחפש באינטרנט: • למשל http://www.cppreference.com/ • יש עוד אוספים ב-STL • למשל set, stack ומבני נתונים נוספים שלא נלמדו בקורס מבוא לתכנות מערכות - 234122

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

  17. כתיבת אלגוריתמים גנריים איטרטורים אלגוריתמים function objects מבוא לתכנות מערכות - 234122

  18. אלגוריתם max template<typenameIterator> Iteratormax(Iterator start, Iterator end) { if (start == end) { return end; } Iterator maximum = start; for(Iterator i = ++start; i != end; ++i) { if (*i > *maximum) { maximum = i;} } return maximum;} • ברצוננו לכתוב פונקציה אשרמוצאת את האיבר המקסימלי • כיצד נוכל לאפשר לפונקציהלעבוד על כל אוסף אפשרי? • דוגמאות לשימוש: • מציאת הערך הגדול ביותר בוקטור • מחיקת האיבר הגדול ביותר ברשימה intm = *max(myVector.begin(), myVector.end()); myList.erase(max(myList.begin(), myList.end())); מבוא לתכנות מערכות - 234122

  19. יתרונות • אלגוריתם יחיד מתאים לכל המקרים: • אוספים שונים • רק חלקים מהאוסף ניתנים למעבר • קל להתאים קוד קיים שיעבוד עם האלגוריתם • למשל שימוש באלגוריתם עבור מערך רגיל של C: int* myArray = newint[size]; //... intm = *max(myArray, myArray+size); מבוא לתכנות מערכות - 234122

  20. algorithm • מלבד מבני הנתונים ה-STL מכיל גם אוסף אלגוריתמים מועילים הניתנים להפעלה על כל מבנה נתונים מתאים • חלקם פשוטים: • וחלקם פחות: template<typenameIterator, typenameT> Iteratorfind(Iterator start, Iterator end, constT& val); template<typenameIterator> voidsort(Iterator start, Iterator end); template<typenameIterator> voidrandom_shuffle(Iterator start, Iterator end); מבוא לתכנות מערכות - 234122

  21. Function Objects • במקום לשלוח מצביעים לפונקציות ב STL-משתמשים ב-“function objects” • Function object הוא כל עצם המעמיס את אופרטור () (אופרטור ההפעלה) • במקרה זה אנו מגדירים אתהמחלקה כפונקציה המקבלתint ומחזירה bool classLessThanZero { public: booloperator()(int m) const { return m < 0; } }; void f() { LessThanZeroisNegative; cout << isNegative(-4) << endl; // true cout << isNegative(6) << endl; // false } מבוא לתכנות מערכות - 234122

  22. Function Objects classSumOf{public:booloperator()(int a, int b, int s) { return a + b == s;}}; • אופרטור ההפעלה ניתן להגדרה עם מספר כלשהו של פרמטרים • בניגוד למצביעים לפונקציות, ניתן לזכור מצב ב-Function Object ולכן ניתן להשתמש באותה מחלקה למס' פונקציות השונות רק בפרמטר כלשהו • שימו לב להבדל בין קריאה לבנאי להפעלת האופרטור המתבצעת על עצם מהטיפוס. voidg() {SumOfisSum;cout << isSum(2,3,5) << endl; // truecout << isSum(1,2,5) << endl; // false} classLessThan{intn;public: LessThan(int n) : n(n) {} booloperator()(int m) const{return m < n;}}; voidh() {LessThanisLess(5);LessThan isLess2(8);cout << isLess(4) << endl; // truecout << isLess(6) << endl; // falsecout << isLess2(6) << endl; // true} מבוא לתכנות מערכות - 234122

  23. Function Objects - דוגמה • נוכל להשתמש בפונקציה שהגדרנו בקריאה לאלגוריתמים מסוימים: template<typenameIterator, typenamePredicate> Iteratorfind_if(Iterator first, Iterator last, Predicatepred) { for (; first != last; first++) if (pred(*first)) break; return first; } vector<int> v; vector<int>::iterator i = find_if(v.begin(),v.end(),LessThan(2)); vector<int>::iterator j = find_if(v.begin(),v.end(),LessThan(7)); מבוא לתכנות מערכות - 234122

  24. דוגמה נוספת • ניצור קריטריון מיון חדש למספרים שלמים כך שנוכל למיין לפי ערך המספר מודולו m: • בכדי למיין פשוט נקרא לאלגוריתםהמיון עם פרמטר נוסף: classLessThanModulo { intm; public: LessThanModulo(int m) : m(m) {} booloperator()(int a, int b) { returna%m < b%m; } }; sort(v.begin(), v.end(), LessThanModulo(5)); מבוא לתכנות מערכות - 234122

  25. הוספת תמיכה באיטרטורים classString { intlength; char* value; public://... typedefchar* iterator; typedefconstchar* const_iterator;iteratorbegin() {returnvalue;} const_iteratorbegin() const{returnvalue;} iteratorend() {returnvalue+length;} const_iteratorend() const{returnvalue+length;}//...}; • ניזכר במחלקת String שהגדרנו בתרגול 9, כיצד נוכל להשתמש באלגוריתמים הקיימים עבור מחלקה זו? • עלינו להוסיף תמיכה באיטרטורים במחלקה. • במקרה של String, נוכל פשוט להשתמש במצביע ל-char • שימו לב להגדרות הטיפוסים שאנו מוסיפים, כדי שהמשתמש לא יצטרךלדעת בעצמו שאנו משתמשים במצביעל-char • לא תמיד ניתן להשתמש בטיפוס קיים כאיטרטור, לפעמים נצטרך להגדיר מחלקה חדשה עבור איטרטור מבוא לתכנות מערכות - 234122

  26. שימוש • האלגוריתם transform מפעיל פונקציה על תחום מסוים ומציב לתחום אחר את ערך ההחזרה • נשתמש בו ובפונקצית הספריה של Ctoupper() כדי להמיר מחרוזת לאותיות גדולות transform(str.begin(),str.end(),str.begin(),toupper); מבוא לתכנות מערכות - 234122

  27. כתיבת אלגוריתמים גנריים - סיכום • הספריה הסטנדרטית בנויה מאוספים ואלגוריתמים • איטרטורים מהווים את הדבק המאפשר לשני חלקים אלו להישאר גנריים • משתמשים ב-Function Objects כדי לאפשר שליחת פונקציות לאלגוריתמים אוספים אלגוריתמים איטרטורים מבוא לתכנות מערכות - 234122

  28. מצביעים חכמים smart_ptr shared_ptr מבוא לתכנות מערכות - 234122

  29. מצביעים חכמים • מצביעים אחראים לרובן המוחלט של שגיאות הזיכרון • הצלחנו להיפטר מרוב השימושים הלא בטוחים במצביעים בעזרת שימוש בתכונות של ++C • ניצול בנאים, הורסים והשמות לטיפול מסודרבזיכרון • שימוש ב-STL ואוספים לטיפול מסודרבאלגוריתמים של מבני הנתונים • אם ננסה להשתמש בהורשה וב-vector מתעוררת בעיה: • עבודה עם מצביעים מוחקת את כל היתרונות שהשגנו! • צריך לנהל זיכרון בצורה מפורשת בכל מקום בתוכנית! vector<Shape> shapes; Circlecircle(3.5); shapes.push_back(circle); vector<Shape*> shapes; //... deleteshapes.back(); shapes.pop_back(); מבוא לתכנות מערכות - 234122

  30. smart_ptr • נוכל ליצור מחלקה, המתנהגת כמצביע אך מוסיפה התנהגות נוספת • למשל, שחרור העצם המוצבעבזמן הריסת המצביע • כלומר ניצור מצביע "חכם" יותר • מה קורה במקרה וה-newהשנינכשל בכל אחת מהפונקציות? template<typenameT> classsmart_ptr { T* data; typedefTelement_type; public:explicitsmart_ptr(T* ptr = NULL) : data(ptr) {} ~smart_ptr() {deletedata; } T& operator*() const{return*data; } T* operator->() const{ returndata;}}; intbad() { Shape* ptr1 =newCircle(5.0); Shape* ptr2 =new Square(5.0); //... } intgood() { smart_ptr<Shape> ptr1(new Circle(5.0)); smart_ptr<Shape> ptr2(new Square(5.0)); //... } מבוא לתכנות מערכות - 234122

  31. העתקה • כיצד יתנהג המצביע במקרה של העתקה? • מה צריכה להיות ההתנהגות עבור בנאי העתקה והשמה? • ניסיון ראשון: העתקת המצביע תבצע "העברת בעלות" של העצם המוצבע • התנהגות זו ממומשת ב-auto_ptr הקיים בספריה הסטנדרטית של ++C • מאפשרת העברת/החזרת עצמים מוקצים דינאמית מפונקציות מבוא לתכנות מערכות - 234122

  32. העתקה • שימוש ב-auto_ptr כדי להחזיר עצם חדש: • בניגוד למצביע רגיל – מוגדר בקוד מי אחראי לשחרור העצם. • שימוש ב-auto_ptr כדי לחסוך העתקות: • בעיה! ההתנהגות עבור "העתקה" של auto_ptrאינה סטנדרטית - ההעתק אינו זהה למקור! • לא ניתן לאחסן auto_ptr בתוך אוספים של ה-STL מאחר והם מסתמכים על תכונה זו • כיצד ניתן ליצור מצביע חכם שגם יאפשר העתקה נכונה? auto_ptr<Shape> createMyShape() { returnauto_ptr<Shape>(new Circle(5.0)); } auto_ptr<vector<int> > getNPrimeNumbers (int n) { // ... } מבוא לתכנות מערכות - 234122

  33. שיפור - מצביע משותף • ניצור מצביע חכם יותר מתקדם: shared_ptr • לכל עצם מוצבע נשמור מונה הצבעות, וכל המצביעים ישתמשו במונה זה על מנתלדעת מתי יש לשחרר את העצם המוצבע Object 2 Object shared_ptr<Object> shared_ptr<Object> shared_ptr<Object> 1 Object* ptr Object* ptr Object* ptr int* counter int* counter int* counter מבוא לתכנות מערכות - 234122

  34. shared_ptr - דוגמה • בעזרת shared_ptr לא נצטרך לדאוג יותר לזכור לשחרר מצביעים, כאשר המצביע האחרון לעצם כלשהו ישתחרר הוא ישחרר את העצם • נוכל לכתוב קוד המשתמש במצביעים בקלות וללא סכנה לבלבול • לדוגמה, בקוד הבא קשה לראות אילו עצמים יש לשחרר ומתי: vector<shared_ptr<Shape> > function(constvector<Point>& points) { shared_ptr<Shape> shape(newPolygon(points)); shared_ptr<Circle> circle(new Circle(5.0)); shared_ptr<Square> square(new Square(2.0));vector<shared_ptr<Shape> > vec; vec.push_back(shape); vec.push_back(circle); vec.push_back(square); vector<shared_ptr<Shape> > copy = vec; copy.pop_back(); return copy;} מבוא לתכנות מערכות - 234122

  35. shared_ptr - מימוש template<typenameT> classshared_ptr { private: T* data; int* counter; typedefTelement_type; voidcheckInvariant() const { assert((data && counter && *counter > 0) || (!data && !counter)); } מבוא לתכנות מערכות - 234122

  36. shared_ptr - מימוש voidincreaseCount() { checkInvariant(); if (counter) { (*counter)++; } } voiddecreaseCount() { checkInvariant(); if (counter && --*counter == 0) { deletecounter; deletedata; counter = NULL; data = NULL; } } מבוא לתכנות מערכות - 234122

  37. shared_ptr - מימוש public: explicitshared_ptr(T* ptr = NULL) : data(ptr), counter(data ? newint(1) : NULL) { } shared_ptr(constshared_ptr<T>& other) : data(other.data), counter(other.counter) { increaseCount(); checkInvariant(); } template<typenameS> friendclassshared_ptr; template<typenameS> shared_ptr(constshared_ptr<S>& other) : data(other.data), counter(other.counter) { increaseCount(); checkInvariant(); } מבוא לתכנות מערכות - 234122

  38. shared_ptr - מימוש ~shared_ptr() { checkInvariant(); decreaseCount(); checkInvariant(); } template<typenameS> shared_ptr<T>& operator=(shared_ptr<S>& other) { checkInvariant(); other.increaseCount(); decreaseCount(); counter = other.counter; data = other.data; checkInvariant(); return *this; } מדוע אין צורך בבדיקת הצבה עצמית? מבוא לתכנות מערכות - 234122

  39. shared_ptr - מימוש T& operator*() const { checkInvariant(); assert(data != NULL); return *data; } T* operator->() const { checkInvariant(); assert(data != NULL); returndata; } T* get() const { checkInvariant(); returndata; } המתודה get() מסופקת בכדי לאפשר שימוש של המחלקה גם עם קוד ישן שאינו תומך ב-shared_ptr מבוא לתכנות מערכות - 234122

  40. shared_ptr - מימוש operator bool() { checkInvariant(); returndata; } voidreset() { decreaseCount(); data = NULL; counter = NULL; } }; המרה לערך בוליאני תאפשר לנו לבדוק האם המצביע תקין כמו מצביע רגיל. המתודה reset() מאפשרת למשתמש צורה נוחה יותר לאיפוס מצביע מבוא לתכנות מערכות - 234122

  41. shared_ptr - מימוש template<typenameT, typenameS> booloperator==(constshared_ptr<T>& first, constshared_ptr<S>& second) { returnfirst.get() == second.get(); } template<typenameT, typenameS> booloperator!=(constshared_ptr<T>& first, constshared_ptr<S>& second) { return !(first == second); } template<typenameT, typenameS> booloperator<(constshared_ptr<T>& first, constshared_ptr<S>& second) { returnfirst.get() < second.get(); } מבוא לתכנות מערכות - 234122

  42. shared_ptr - סיכום • יתרונות: • כל הקוד לניהול הזיכרון נכתב רק פעם אחת • קל לחלוק עצמים אשר לא ברור מי האחרון שמשתמש בהם • ניתן להשתמש בכל האוספים שראינו עם מצביעים ובפרט עם ירושות • הקוד נהייה פשוט יותר ועמיד יותר בפני שגיאות זיכרון • עדיין קיימות מספר בעיות: • shared_ptr אינו מתאים לאחסון מצביע למערך! • כי השחרור הוא ע"י delete ולא delete[] • ניתן ליצור בקלות shared_array שיתאים לבעיה זו, אבל ממילא עדיף vector! • המצביעים לא ישוחררו אם קיימים קשרים מעגליים • במקרה זה יש לנקות את המצביעים בצורה מפורשת עם reset • פתרון טוב יותר למקרה זה דורש היכרות עם מצביע חכם נוסף והזמן קצר מלהכיל אותו מבוא לתכנות מערכות - 234122

More Related