תרגול 12 standard template library כתיבת אלגוריתמים גנריים מצביעים...

Post on 19-Dec-2015

229 Views

Category:

Documents

5 Downloads

Preview:

Click to see full reader

TRANSCRIPT

12תרגול

Standard Template Libraryכתיבת אלגוריתמים גנריים

מצביעים חכמים

ספרית התבניות הסטנדרטית

Cקיימת בכל מימוש של ++• ואלגוריתמים.(Containers)מכילה אוספים •

:(templates)משתמשת בתבניות •אוספי הנתונים גנריים–האלגוריתמים המסופקים גנריים–מאפשרת הרחבה ע"י המשתמש–שומרת על ביצועים גבוהים–

2

Vector

נכיר תחילה את האוסף הפשוט ביותר בספריה – •vector.

מערך סדור של איברים–מאפשר גישה לכל איבר באוסף–מאפשר הוספת והסרת איברים–

הערה: הקוד בשקפים הוא חלקי וחסרות בו •.STLתכונות נוספות ומתקדמות יותר של אוספי ה-

דוגמאות הקוד מתרכזות בשימוש בסיסי ופשוט.

3

<vectortemplate<typename Tמתודות נבחרות של

class vector {

vector();

vector(const vector& c);

vector(size_t num, const T& val = T());

~vector();

 

T& operator[](size_t index);

const T& operator[](size_t index) const;

vector operator=(const vector& v);

 

T& at(size_t loc);

const T& at(size_t loc) const;

 

void pop_back();

void push_back(const T& val);

size_t size() const;

};

גישה בעזרת הערה:•אופרטור ][ אינה בטוחה

אינה מוודאת את •חוקיות האינדקס

לעומת זאת, גישה • זורקת )(atבעזרת

חריגה במקרה והאינדקס אינו חוקי

out_of_rangeמסוג •

4

vectorדוגמאות לשימוש ב-int main() {

vector<int> v1; // empty vectorvector<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();}const vector<int> copy(v1);//Safely copies a vector

return 0;}

5

יתרונות

ניהול זיכרון ע"י המחלקה•לא צריך לזכור לשחרר ידנית את המערך–ניתן לשנות את גודל המערך בקלות–ניתן ליצור העתקים ולבצע השמות בקלות–

גישה בטוחה•)(atרק כאשר משתמשים ב-–

6

שאלה: כיצד ניתן ליצור וקטור בטוח?בד"כ מחיר בדיקת גישה לוקטור אינו משפיע •

על ביצועי התכנית.

ניצור גרסה של וקטור המוודאת לכל גישה •האם היא חוקית:

template<typename T>class SafeVector : public vector<T> {

SafeVector() : vector<T>() {}SafeVector(int s) : vector<T>(s) {}

T& operator[](int i) { return at(i); }const T& operator[](int i) const { return at(i); }

};

7

List

דומה לוקטור, אך המימוש הוא ברשימה מקושרת.•שימוש ברשימה מקושרת מאפשר הוספת איברים מהירה –

יותר, אך אינו מאפשר גישה מהירה לאמצע האוסף.

הוא במימוש, לכן לשני vector ל-listההבדל העיקרי בין –האוספים מנשק דומה.

השימוש במנשק דומה מאפשר למשתמש להחליף בקלות –את סוג האוסף בשימוש גם בשלבים מאוחרים.

-כאמור, סיבוכיות היא שיקול משני בד"כ ולכן ניתן להשתמש בvector ,ורק בהמשך כשיש צורך ברשימה מקושרת ניתן לעבור לשימוש בה.

8

listמתודות נבחרות מ-template<typename T>

class list {

list();

list(const list& c);

list(size_t num, const T& val = T());

~list();

 

list operator=(const list& v);

 

void pop_back();

void push_back(const T& val);

void pop_front();

void push_front(const T& val);

 

size_t size() const;

};

נוספו מתודות להסרת •והוספת איברים גם

בראש הרשימה

נעלמו המתודות לגישה •לאיבר באמצע

הרשימה

כיצד נוכל לאפשר •גישה למשתמש בצורה

נוחה?

9

Iterators.Iteratorע"מ לאפשר מעבר סדור על איברי אוסף נשתמש בעצם מסוג •

)בינתיים(:Iteratorנדרוש את הפעולות הבאות מ-•קידום – שינוי האיטרטור כך שיצביע לאיבר הבא–

קריאה – החזרת האיבר המוצבע ע"י האיטרטור–

השוואה – בדיקה האם שני איטרטורים מצביעים לאותו איבר–

איטרטור אינו מחלקה,•

.(concept)אלא מושג

למשל מצביע הוא איטרטור

:Cעבור מערך של

int* array = new int[n];...int* i = array;cout << *i << endl; // Reading the iteratori++; // Advancing the iterator by 1if (i == array+n) { // Comparing to iterators

cout << "End of array!" << endl;}

10

Iterators & STL

מספקים STLכל האוספים המאפשרים מעבר על איבריהם ב-•iterator.בעזרתו ניתן לעבור על איבריהם

מתאים לתחילת האוסף.iterator מחזירה )(beginהמתודה • מתאים לאיבר "דמה" אחרי iterator מחזירה )(endהמתודה •

האיבר האחרון.

למשתמש לא אכפת כיצד ממומש האיטרטור!•

11

דוגמה לשימוש בוקטור

:vectorהדפסת איברי

for(vector<int>::iterator i = v.begin();i != v.end(); ++i) {

cout << *i << endl;

}

:listהדפסת איברי

for(list<int>::iterator i = l.begin(); i != l.end(); ++i) {

cout << *i << endl;

}

STLבצורה דומה ניתן לגשת לכל אוסף ב-• מוגדר בתוך כל אוסף בצורה iteratorהטיפוס עבור ה-•

הבאה:

container<int>::iterator12

const_iterator

בנוסף כל אוסף מגדיר גם •.const_iteratorטיפוס

כך ניתן להגן על האוסף –מפני שינויים כאשר הוא

constמוגדר כ-

טיפוס האיטרטור המוחזר –נקבע בעזרת העמסה על

המתודות המתאימות

vector<int> v;vector<int>::iterator i = v.begin();*i = 7; // O.K.!!

// *i is int&

const vector<int> v;vector<int>::const_iterator i = v.begin();*i = 7; // syntax error!

// *i is const int&

13

עוד שימושיםרוב המתודות עבור האוספים משתמשות •

באיטרטורים כדי לציין מיקומים.הוספת איבר לתוך רשימה ממוינת:–

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);

14

סוגי איטרטורים

אוספים שונים מחזירים איטרטורים שונים.•

bidirectional iterator, למשל, יש listל-•המאפשר קידום האיטרטור עם אופרטור++

והחזרתו לאחור עם אופרטור--.

המאפשר random access iterator יש vectorל-•גם חיבור מספרים לאיטרטור וחישוב הפרש

בין שני איטרטורים )בדומה למצביע(.

15

?STLעוד

מס' רב של מתודות ואופרטורים list ו-vectorל-•נוספים.

כמעט כל פעולה שעולה בדעתכם לבצע כבר –.STLממומשת ב-

ניתן פשוט לחפש באינטרנט:– למשלhttp://www.cppreference.com/.

.STLיש עוד אוספים ב-• ומבני נתונים נוספים שלא נלמדו set, stackלמשל –

בקורס.

16

כתיבת אלגוריתמים גנריים

ברצוננו לכתוב פונקציה אשר מוצאת את •האיבר המקסימלי.

כיצד נוכל לאפשר לפונקציה לעבוד על כל •אוסף אפשרי?

17

maxאלגוריתם template<typename Iterator>

Iterator max(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;

}

18

- שימושיםmaxאלגוריתם

דוגמאות לשימוש:•מציאת הערך הגדול ביותר בוקטור.–

int m = *max(myVector.begin(), myVector.end());

מחיקת האיבר הגדול ביותר ברשימה.–myList.erase(max(myList.begin(), myList.end()));

19

יתרונות

אלגוריתם יחיד מתאים לכל המקרים:•אוספים שונים–רק חלקים מהאוסף ניתנים למעבר–קל להתאים קוד קיים שיעבוד עם האלגוריתם–

למשל שימוש באלגוריתם עבור מערך רגיל שלC:

int* myArray = new int[size];

...

int m = *max(myArray, myArray+size);

20

algorithm

מכיל גם אוסף STLמלבד מבני הנתונים ה-•אלגוריתמים מועילים הניתנים להפעלה על כל

מבנה נתונים מתאים:

חלקם פשוטים:•Iterator find(Iterator start, Iterator end, const T& val);

וחלקם פחות:•void sort(Iterator start, Iterator end);

void random_shuffle(Iterator start, Iterator end);

21

Function Objects

STLבמקום לשלוח מצביעים של פונקציות ב-•.”Function objects“משתמשים ב-

•Function object הוא כל עצם המעמיס את אופרטור )( )אופרטור ההפעלה(

22

} Function Objectclass LessThanZeroדוגמה ל-

public:

bool operator()(int m) const {

return m < 0;

}

};

int main() {

LessThanZero isNegative;

 

cout << isNegative(-4) << endl; // true

cout << isNegative(6) << endl; // false

 

return 0;

}

את המחלקה מגדירים •עם אופרטור ההפעלה.

במקרה זה אנו מגדירים •את המחלקה כפונקציה

ומחזירה intהמקבלת bool.

23

} Function Objectclass SumOfדוגמה ל-

public:

bool operator()(int a, int b, int s) {

return a+b == s;

}

};

int main() {

SumOf isSum;

 

cout << isSum(2,3,5) << endl; // true

cout << isSum(1,2,5) << endl; // false

 

return 0;

}

אופרטור ההפעלה ניתן •להגדרה עם כל מספר

של פרמטרים.

24

} Function Objectclass LessThanדוגמה ל-

public:

LessThan(int n) : n(n) {}

  bool operator()(int m) const{

return m < n;

}

private:

int n;

};

int main() {

LessThan isLess(5);

LessThan isLess2(8);

  cout << isLess(4) << endl; // true

cout << isLess(6) << endl; // false

cout << isLess2(6) << endl; // true

  return 0;

}

עד עכשיו לא הצגנו את היתרון על •שימוש במצביע לפונקציה:

בניגוד למצביעים לפונקציות, ניתן • ולכן Function Objectלזכור מצב ב-

ניתן להשתמש באותה מחלקה למס' פונקציות השונות רק

בפרמטר כלשהו.

)היזכרו כיצד פתרנו את אותה •(Cבעיה ב-

שימו לב להבדל בין קריאה לבנאי •להפעלת האופרטור המתבצעת

על עצם מהטיפוס.

25

Function Objectsדוגמה - נוכל להשתמש בפונקציה שהגדרנו בקריאה •

לאלגוריתמים מסוימים:template<typename Iterator, typename Predicate>

Iterator find_if(Iterator first, Iterator last, Predicate pred) {

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));

26

Function Objectsדוגמה נוספת ל-ניצור קריטריון מיון חדש למספרים שלמים, נמיין •

:mלפי ערך המספר מודולו class LessThanModulo {

public:

LessThanModulo(int m) : m(m) {}

bool operator()(int a, int b) {return a%m < b%m;}

private:

int m;

};

בכדי למיין פשוט נקרא לאלגוריתם המיון עם •פרמטר נוסף:

sort(v.begin(), v.end(), LessThanModulo(5));

27

הוספת איטרטורים

, 9 שהגדרנו בתרגול Stringניזכר במחלקת •כיצד נוכל להשתמש באלגוריתמים הקיימים

עבור מחלקה זו?

עלינו להוסיף תמיכה באיטרטורים במחלקה.•

28

Stringהוספת תמיכה באיטרטורים ב-class String {

private:

int length;

char* value;

 

public:

...

  typedef char* iterator;

typedef const char* const_iterator;

 

iterator begin() { return value; }

const_iterator begin() const { return value; }

iterator end() { return value+length; }

const_iterator end() const { return value+length; }

...

};

, נוכל פשוט להשתמש Stringבמקרה של •.charבמצביע ל-

שימו לב להגדרות הטיפוסים שאנו •מוסיפים, כדי שהמשתמש לא יצטרך לדעת

charבעצמו שאנו משתמשים במצביע ל-

29

שימוש

מפעיל פונקציה על תחום transformהאלגוריתם •מסוים ומציב לתחום אחר את ערך ההחזרה.

)(C toupperנשתמש בו ובפונקצית הספריה של •כדי להמיר מחרוזת לאותיות גדולות.

transform(str.begin(),str.end(),str.begin(),toupper);

30

סיכום

הספריה הסטנדרטית בנויה מאוספים •ואלגוריתמים

איטרטורים מהווים את הדבק המאפשר לשני •חלקים אלו להישאר גנריים

אוספיםאוספים איטרטוריםאיטרטוריםאלגוריתמיםאלגוריתמים

31

מצביעים חכמים

מצביעים אחראים לרובן המוחלט של שגיאות •הזיכרון

הצלחנו להיפטר מרוב השימושים הלא בטוחים •Cבמצביעים בעזרת שימוש בתכונות של ++

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

ואוספים לטיפול מסודר STLשימוש ב-–באלגוריתמים של מבני הנתונים.

32

בעיהvector<Shape> shapes;Circle circle(3.5);shapes.push_back(circle);// Only the "Shape part" // is copied!!

vector<Shape*> shapes;...delete shapes.back();shapes.pop_back();// We must delete ourselves// when removing items from// the vector

ירושות דורשות שימוש •במצביעים

עבודה עם מצביעים •מוחקת את כל

היתרונות שהשגנו!צריך לנהל זיכרון בצורה –

מפורשת בכל מקום בתוכנית!

33

smart_ptr

נוכל ליצור מחלקה, המתנהגת כמצביע אך •מוסיפה התנהגות נוספת.

למשל, שחרור העצם המוצבע בהריסת המצביע.–

כלומר ניצור מצביע "חכם" יותר.•

34

smart_ptrtemplate<typename T>

class smart_ptr {

private:

T* pointedData;

typedef T element_type;

public:

explicit smart_ptr(T* ptr = NULL) : pointedData(ptr) {}

~smart_ptr() {

delete pointedData;

}

T& operator*() const {

return *pointedData;

}

T* operator->() const {

return pointedData;

}

};

35

smart_ptrיתרונות שימוש ב-

השני נכשל בכל newמה קורה במקרה וה-•אחת מהפונקציות?

int bad() {

Shape* ptr1 =

new Circle(5.0);

Shape* ptr2 =

new Square(5.0);

...

}

int good() {

smart_ptr<Shape>

ptr1(new Circle(5.0));

smart_ptr<Shape>

ptr2(new Square(5.0));

...

}36

העתקה

כיצד יתנהג המצביע במקרה של העתקה?•מה צריכה להיות ההתנהגות עבור בנאי העתקה –

והשמה?

העתקת המצביע תבצע "העברת ניסיון ראשון:•בעלות" של העצם המוצבע

הקיים בספריה auto_ptrהתנהגות זו ממומשת ב-–.Cהסטנדרטית של ++

מאפשרת העברת/החזרת עצמים מוקצים דינאמית –מפונקציות.

37

auto_ptrשימוש ב-

כדי להחזיר עצם חדש:auto_ptrשימוש ב-•בניגוד למצביע רגיל – מוגדר בקוד מי אחראי –

לשחרור העצם.auto_ptr<Shape> createMyShape() {

return auto_ptr<Shape>(new Circle(5.0));

}

כדי לחסוך העתקות:auto_ptrשימוש ב-•auto_ptr<vector<int>> getNPrimeNumbers (int n) {

...

}

38

העתקה

auto_ptrבעיה! ההתנהגות עבור "העתקה" של •אינה סטנדרטית

ההעתק אינו זהה למקור!– לא ניתן לאחסןauto_ptr-בתוך אוספים של ה STL מאחר

והם מסתמכים על תכונה זו.

כיצד ניתן ליצור מצביע חכם שגם יאפשר •העתקה נכונה?

39

שיפור – מצביע משותף

ניצור מצביע חכם יותר •shared_ptr מתקדם:

לכל עצם מוצבע נשמור •מונה הצבעות, וכל

המצביעים ישתמשו במונה זה על מנת לדעת

מתי יש לשחרר את העצם המוצבע.

ObjectObject

ObjectObject 11

22

Object* ptrint* counter

shared_ptr<Object>

Object* ptrint* counter

shared_ptr<Object> Object* ptrint* counter

shared_ptr<Object>

40

משתנים ואינווריאנטהtemplate<typename T>

class shared_ptr {

private:

T* pointedData;

int* referenceCounter;

 

typedef T element_type;

 

void checkInvariant() const {

assert((pointedData &&

referenceCounter &&

(*referenceCounter)>0) ||

(!pointedData && !referenceCounter));

}

 

41

עדכון המונהvoid increaseCount() {

checkInvariant();

if (referenceCounter) {

(*referenceCounter)++;

}

}

 

void decreaseCount() {

checkInvariant();

if(referenceCounter && --*referenceCounter == 0){

delete referenceCounter;

delete pointedData;

referenceCounter = NULL;

pointedData = NULL;

}

}

42

אתחול ושחרורpublic:

 

explicit shared_ptr(T* ptr = NULL) :

pointedData(ptr),

referenceCounter(pointedData ? new int(1) : NULL){}

 

shared_ptr(const shared_ptr<T>& other) :

pointedData(other.pointedData),

referenceCounter(pointedData ? other.referenceCounter : NULL) {

increaseCount();

checkInvariant();

}

43

אתחול ושחרורtemplate <typename S> friend class shared_ptr;

template <typename S> shared_ptr(const shared_ptr<S>& other) :

pointedData(other.pointedData),

referenceCounter(pointedData ? other.referenceCounter : NULL) {

increaseCount();

checkInvariant();

}

 

~shared_ptr() {

checkInvariant();

decreaseCount();

checkInvariant();

}

מטרת הבנאי היא •לאפשר השמה בין

shared_ptr של טיפוסים שונים,

למשל בגלל הורשה.

44

גישה למצביעT& operator*() const {

checkInvariant();

assert(pointedData != NULL);

return *pointedData;

}

T* operator->() const {

checkInvariant();

assert(pointedData != NULL);

return pointedData;

}

T* get() const {

checkInvariant();

return pointedData;

}

מסופקת )(getהמתודה •בכדי לאפשר שימוש של המחלקה גם עם קוד ישן

שאינו תומך shared_ptrב-

45

אופרטור השמהtemplate<typename S> shared_ptr<T>& operator=(

shared_ptr<S>& other) {

checkInvariant();

other.increaseCount();

decreaseCount();

referenceCounter = other.referenceCounter;

pointedData = other.pointedData;

checkInvariant();

return *this;

}

מה קורה במקרה של הצבה עצמית?•

מדוע אין צורך בבדיקה?•

46

operator bool() {

checkInvariant();

return pointedData;

}

 

void reset() {

decreaseCount();

pointedData = NULL;

referenceCounter = NULL;

}

 

};

המרה לערך בוליאני •תאפשר לנו לבדוק האם

המצביע תקין כמו מצביע רגיל.

)(resetהמתודה •מאפשרת למשתמש

צורה נוחה יותר לאיפוס מצביע

עוד מתודות שימושיות

47

פונקציות השוואהtemplate <typename T, typename S>

bool operator==(const shared_ptr<T>& first,

const shared_ptr<S>& second) {

return first.get() == second.get();

}

 

template <typename T, typename S>

bool operator!=(const shared_ptr<T>& first,

const shared_ptr<S>& second) {

return !(first==second);

}

 

template <typename T, typename S>

bool operator<(const shared_ptr<T>& first,

const shared_ptr<S>& second) {

return first.get() < second.get();

}

48

דוגמאות

vector<shared_ptr<Shape> > function() {

shared_ptr<Shape> shape(new Polygon(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;

}

את האחרון שיוצא סוגר ההעתקות בטוחות! כל•האור!

תרגיל לבית •)למשועממים(:

מיצאו את הצורה שיש

למחוק בסוף הפונקציה...

איך היה נראה הקוד ללא •שימוש במצביעים

49חכמים?

יתרונות

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

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

מצביעים ובפרט עם ירושות.הקוד נהייה פשוט יותר ועמיד יותר בפני שגיאות •

זיכרון.מה קורה בדוגמה הקודמת אם אחת מהקצאות –

הזיכרון נכשלת? מה היה קורה ללא המצביעים החכמים?

50

לא הכל מושלם!

•shared_ptr!אינו מתאים לאחסון מצביע למערך .][delete ולא deleteכי השחרור הוא ע"י – שיתאים לבעיה זו.shared_arrayניתן ליצור בקלות –

-אבל ממילא עדיף להשתמש בvector!

המצביעים לא ישוחררו אם קיימים קשרים •מעגליים

במקרה זה יש לנקות את המצביעים בצורה מפורשת –.resetעם

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

51

לסיכום

היא מסובכת.Cכתיבת ספריות ב-++•

השימוש בהן לעומת זאת הוא פשוט.•מאפשר כתיבת קוד טוב יותר בפחות זמן ופחות שורות.–

באתר shared_ptr של פשוט מימוש למצוא ניתן•הקורס.

52

top related