תרגול מס
This presentation is the property of its rightful owner.
Sponsored Links
1 / 38

תרגול מס ' 10 PowerPoint PPT Presentation


  • 96 Views
  • Uploaded on
  • Presentation posted in: General

תרגול מס ' 10. הורשה פולימורפיזם. הורשה. דוגמה תחביר יחס is-a החלפת פונקציות. דוגמה. מנשק גרפי מורכב מרכיבים שונים הקרויים Widgets לכל הרכיבים יש תכונות משותפות למשל רוחב, גובה, מיקום לרכיבים מסוימים יש תכונות ייחודיות פעולה המתבצעת לאחר לחיצה על Button

Download Presentation

תרגול מס ' 10

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.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


10

תרגול מס' 10

הורשה

פולימורפיזם


10

הורשה

דוגמה

תחביר

יחס is-a

החלפת פונקציות

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


10

דוגמה

  • מנשק גרפי מורכב מרכיבים שונים הקרויים Widgets

  • לכל הרכיבים יש תכונות משותפות

    • למשל רוחב, גובה, מיקום

  • לרכיבים מסוימים יש תכונות ייחודיות

    • פעולה המתבצעת לאחר לחיצה על Button

    • קריאת הערך השמור ב-Entry

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

Entry

Radio button

Check button

Button

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


10

דוגמה

  • אם ננסה לממש מחלקות עבור Widgets ניתקל במספר בעיות:

    • שכפול קוד בין ה-Widgets השונים

    • אין דרך נוחה להחזיק את כל ה-Widgets בתוך Window

  • איך היינו פותרים את הבעיה ב-C? מה החסרונות של פתרון זה?

classButton {

intx, y;

intwidth, height;

stringtext;

//...

public:

intgetWidth();

intgetHeight();

voidonClick();

//...

};

classEntry {

intx, y;

intwidth, height;

stringtext;

//...

public:

intgetWidth();

intgetHeight();

stringgetValue();

//...

};

classWindow {

Array<Button> buttons;

Array<Entry> entries;

// ... for every type

// ...

};

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


10

הורשה

  • ניתן להשתמש בהורשהכדי לציין שמחלקה מסוימת יורשת את התכונות של מחלקת בסיס (מחלקת אב) כלשהי

  • ניצור מחלקה עבור Widget אשר תייצג את התכונות המשותפות לכל ה-Widgets

  • כל Widget יירש את התכונות המשותפות ויוסיף את התכונות הייחודיות לו

classWidget {

intx, y, width, height;

stringtext;

// ...

public:

intgetWidth();

intgetHeight();

// ...};

classButton : publicWidget {

// ...

public:

voidonClick();

// ...};

classEntry : publicWidget {

// ...

public:

stringgetValue();

// ...};

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


10

הורשה - תחביר

classA {

public:intn;

intf() { returnn; }};classB : publicA{

public:intm;

intg() { returnm + n; }};

intmain() {

Bb;

b.n = 5;

b.m = 4;

cout << b.f() << endl;

cout << b.g() << endl;

return 0;}

  • כדי לציין שמחלקה B יורשת את מחלקה Aנוסיף אחרי שם המחלקה B“: public A”

  • לעצם מטיפוס B יהיו את כל השדות והמתודות המוגדרים ב-A בנוסף לאלו המוגדרים בו

  • נהוג לסמן הורשה בתרשימים על ידי חץ היוצא מהמחלקה היורשת אל מחלקת האב, למשל:

A

Widget

B

Button

Entry

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


10

בקרת גישה בהורשה

classA{intn;

protected:intf() { returnn; }

public:voidset(int i) { n = i; }};classB : publicA {

public:intg() { return f(); }

};intmain() {

Bb;

b.n = 5; // error

b.set(5); // o.k.

b.f(); // error

cout << b.g() << endl;

return 0;}

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

  • ניתן להגדיר שדות ומתודות כ-protected, מתודות ושדות אלו יהיו:

    • נגישים ממחלקות יורשות

    • אינם נגישים משאר הקוד

  • בעזרת protected ניתן להגדיר מנשק נפרד לקוד המשתמש במחלקה על ידי הורשה מהמנשק לקוד חיצוני

  • כמו תמיד, נעדיף להסתיר את מימוש המחלקה:

    • נעדיף protected על public

    • נעדיף private על protected

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


10

בקרת גישה בהורשה

classA {

// ...

};

classB : publicA {

// ...

};

classC : publicB {

// ...

};

A

private members

protected members

public members

B

private members

protected members

public members

C

private members

protected members

public members

User code

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


10

הורשה - is a

classA {

intn;

public:

intf() { returnn; }

voidset(int i) { n = i; }

};classB : publicA {

public:

intg() { returnf() + 1; }

};

voidh(A& a) {

a.f();

}

intmain() {

Bb;

A& a = b; // o.k.

A* ptra = new B; // o.k.

h(b);

return 0;}

  • הורשה מממשת יחס “is a”בין המחלקה היורשת למחלקת האב

    • “a button is a widget” - כפתור הוא רכיב גרפי

  • אם B יורש מ-A נוכל להשתמש בעצם מטיפוס B בכל מקום בו ניתן להשתמש בעצם מטיפוס A

    • B&יוכל לשמש כ-A&

    • כתובת של עצם מסוג Bיכולה לשמש ככתובת של עצם מסוג A

  • בעזרת הורשה נוכל להתייחס לעצמים מטיפוסים שונים דרך החלק המשותף להם

    • Array<Widget*> יחיד יאחסן את כל ה-widgets

B הוא סוג של A

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


10

הורשה - בנאים והורסים

classA {

intn;

public:

A(int n) : n(n) {}

A() : n(0) {}

};

classB : publicA {

intm;

public:

B(int n) : A(n), m(n) {}

B() : m(1) {}

};

  • בנאים והורסים אינם עוברים בירושה

  • אתחול ושחרור מחלקת הבסיס מתבצע בדומה לאתחול ושחרור שדות

  • ברשימת האתחול של B נקרא הבנאי של A

    • מחלקת הבסיס מאותחלת לפני השדות

    • אם לא מופיעה קריאה מפורשת לבנאי של A ייקרא A()

      • אם לא מוגדר בנאי A() תתקבל שגיאת קומפילציה

    • B אינו מאתחל שדות של A, אתחולם מתבצע על ידי A

  • לאחר הריסת כל השדות של של B ייקרא ~A()

  • בנאי ההעתקה ואופרטור= הנוצרים על ידי הקומפיילר קוראים להעתקה/השמה של מחלקת האב

הקומפיילר יוסיף קריאה ל-A()

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


10

הורשה - זמני קריאה

classX{

intn;

public:

X(int n) : n(n) {

cout<< "X::X():" << n << endl;}

~X() {

cout<< "X::~X():" << n << endl;}};classA{

Xx1;

public:

A(int n) : x1(n) {

cout<< "A::A()" << endl;}

~A() {

cout<< "A::~A()" << endl;}};

  • מה מדפיס הקוד הבא?

classB : publicA {

Xx2;

public:

B(int m, int n) : A(m), x2(n) {

cout<< "B::B()" << endl;

}

~B() {

cout<< "B::~B()" << endl;

}

};

intmain() {

B b(1, 2);

cout << "=========" << endl;

return 0;

}

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


Multistack

דוגמה - MultiStack

  • ברצוננו ליצור את המחלקה MultiStackאשר תומכת בכל הפעולות של מחסנית רגילה (push, pop, top) ובפעולה נוספת popkאשר מוציאה את k האיברים האחרונים שהוכנסו למחסנית

  • קיימות שלוש דרכים לפתרון הבעיה:

    • כתיבת MultiStack מחדש

      • שכפול קוד ועבודה מיותרת

    • שימוש ב-Stack כשדה של MultiStack

      • נצטרך "לתפור" ידנית את המתודות

    • MultiStack תירש את Stack

      • קוד אשר עובד עם Stack יוכל לעבודגם עםMultiStack

classStack{int* data;

intsize;

intnextIndex;public:Stack(int size = 100);

Stack(constStack& stack);

~Stack();

Stack& operator=(constStack& s);voidpush(constint& n);

voidpop();

int& top();

constint& top()const;

intgetSize()const;classFull{};classEmpty{};};

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


Multistack1

MultiStack

classMultiStack : publicStack {

public:

MultiStack(int size);

voidpopk(int k);

classNotEnoughNumbers {};

};

MultiStack::MultiStack(int size)

: Stack(size) {}

voidMultiStack::popk(int k) {

if (getSize() < k) {

throwNotEnoughNumbers();

}

for(int i = 0; i < k; ++i ) {

pop();

}

}

  • עלינו לממש ב-MultiStack רק את הבנאי, popk וחריגה חדשה

  • האם התשובה משתנה אם השדה nextIndexהיה מוגדר כ-protected?

  • השימוש ב-protected יכול לעזור ליורשים אך מוסיף תלויות במימוש

    • בדרך כלל נעדיף להימנע מחשיפת המימוש למחלקות יורשות

voidMultiStack::popk(int k) {

if (nextIndex < k) {

throwNotEnoughNumbers();}nextIndex-= k;}

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


10

הגדרה מחדש של מתודות

classPoint{

intx, y;

public:Point(int x, int y);

voidprint() const;};voidPoint::print() const {

cout << x << "," << y << endl;}

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

    • פעולה זו קרויה overriding

  • במקרה זה הפונקציה של מחלקת האב מוסתרת על ידי הפונקציה החדשה

  • כדי לקרוא לפונקציה הישנה מציינים את ה-namespace של מחלקת האב לפני הקריאה לפונקציה

classLabeledPoint : publicPoint {

stringlabel;

public:LabeledPoint(string s, int x, inty);

voidprint() const;};voidLabeledPoint::print() const {

cout << label << ": ";

Point::print();}

voidf() {

LabeledPoint p("origin", 0, 0);

p.print(); // origin: 0,0}

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


10

הורשה סיכום

  • מחלקה B יכולה לרשת את התכונות של מחלקה A על ידי שימוש בהורשה

  • אם B יורשת מ-A היא מקבלת את כל השדות והמתודות שלה

  • לא ניתן לגשת מ-B לחלקים פרטיים של A

  • ניתן להגדיר שדות ומתודות כ-protected כדי לאפשר למחלקה יורשת לגשת אליהם

  • אם B יורשת מ-A אז B is an A וניתן להשתמש ב-B& כ-A& וב-B* כ-A*

  • אתחול ושחרור מחלקת הבסיס מתבצע כמו אתחול ושחרור של שדה

  • מומלץ להסתיר את מימוש המחלקה ככל הניתן, גם ממחלקות יורשות

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

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


10

פולימורפיזם

פונקציות וירטואליות

מחלקות מופשטות (Abstract classes)

חריגות והורשה

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


10

פולימורפיזם

  • פולימורפיזם(רב-צורתיות) מאפשרת יצירת קוד יחיד המתאים לטיפוסים שונים

  • אם B נראה כמו Aומתנהג כמו Aאז הוא יוכל להחליף את A

  • ניתן לכתוב קוד פולימורפיעבור טיפוס A אשר יעבודלכל טיפוס B אשר יורש מ-A

  • נשתמש בהורשה כדי לייצג חיות שונות בעזרת מחלקות כך שנוכל לכתוב קוד פולימורפי עבור כל סוגי החיות

חיה:משקל, גובה, ת. לידה, הדפס סוגי מזון, השמע קול

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


10

פולימורפיזם

classAnimal {

intage, weight;

public:

Animal(int age, int weight);

intgetAge() const;

voidmakeSound() const {

cout << endl;

} // no sound by default;

};classDog: publicAnimal {

public:

Dog(int age, int weight);

voidmakeSound() const {

cout << "vufvuf" << endl;

}

};

  • ניצור את המחלקה Animal ואת המחלקה Dog אשר תירש ממנה

  • מה ידפיס הקוד הבא?

  • בעיה:

    • הקומפיילר בוחר איזו פונקציה תיקרא בזמן הקומפילציה (קישור סטטי)

    • הפונקציה שאנו רוצים שתרוץ תלויה בטיפוס בזמן הריצה

Dog* d = new Dog(3,4);

Animal* a = d;

a->makeSound();

d->makeSound();

Animal::makeSound

Dog::makeSound

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


10

פונקציות וירטואליות

classAnimal {

intage, weight;

public:

Animal(int age, int weight);

intgetAge() const;

virtual voidmakeSound() const {

cout << endl;

} // no sound by default;

};classDog: publicAnimal {

public:

Dog(int age, int weight);

voidmakeSound() const {

cout << "vufvuf" << endl;

}

};

  • ניתן להכריז על פונקציה כוירטואלית במחלקת האב:

    • במקרה זה הקומפיילר ייצור קוד אשר יבחר את הפונקציה המתאימה לטיפוס בזמן הריצה (קישור דינאמי)

  • כעת בקריאה a->makeSound()תתבצע הפונקציה המתאימה

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

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


10

פונקציות וירטואליות

classDog: publicAnimal {

public:

Dog(int age, int weight);

voidmakeSound() const {

cout << "vufvuf" << endl;

}

};classCat: publicAnimal {

public:

Cat(int age, int weight);

voidmakeSound() const {

cout << "miao" << endl;

}

};classFish: publicAnimal {

public:

Fish(int age, int weight);

}; // the default makeSound is OK

voidfoo() {

Animal* animals[3];

animals[0] = new Dog(3,4);

animals[1] = new Fish(1,1);

animals[2] = new Cat(2,2);

for (int i = 0; i < 3; ++i) {

animals[i]->makeSound();

delete animals[i];

}

}

vufvuf

miao

מה ייקרא כאן?

classAnimal {

intage, weight;

public:

Animal(int age, int weight);

virtual~Animal() {}

// ...

};

אם מתכננים להשתמש בפולימורפיזם חייביםליצור הורס וירטואלי

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


10

מחלקות אבסטרקטיות

  • במקרים רבים מחלקת האב אינה טיפוס שלם בפני עצמה

    • ניתן להגדיר פונקציה כ-pure virtualעל ידי הוספת “= 0”בסוף הכרזתה

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

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

    • לא ניתן ליצור עצם מטיפוס המחלקה

    • חייבים ליצור עצם מטיפוס היורש ממנה

  • ניתן ליצור מצביעים ורפרנסיםלמחלקות אבסטקרטיות

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

classShape {

intcenter_x, center_y;

public:

Shape(int x, int y) :

center_x(x), center_y(y) {}

virtual~Shape() {}

virtualdoublearea() const = 0;

};

area היא פונקציה וירטואלית טהורה

Shape היא מחלקה אבסטרקטית, לא ניתן ליצור עצם מטיפוס Shape

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


10

מחלקות אבסטרקטיות

classShape{intcenter_x, center_y;

public:

Shape(int x, int y) :

center_x(x), center_y(y) {}

virtual~Shape() {}

virtualdoublearea() const = 0;};

classCircle : publicShape {intradius;

public:

Circle(int x, int y, int radius) :

Shape(x,y), radius(radius) {}

virtualdoublearea() const {

returnradius*radius*PI;}};

classSquare : publicShape {intedge;

public:

Square(int x, int y, int edge) :

Shape(x,y), edge(edge) {}

virtualdoublearea() const {

returnedge*edge;}};

void foo() {Shape* shapes[N];

// an array of squares & circles// initialization ...

doubletotalArea=0;

for (int i = 0; i < N; ++i) {

totalArea += shapes[i]->area(); }cout << totalArea << endl;}

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


10

שליחת הודעות לעצמים

classWidget {

//...

virtualvoidredraw();

};

classWindow {

Array<Widget*> children;

//...

public:

voidredraw();

};

voidWindow::redraw() {

for(inti=0;i<children.size();++i) {

children[i]->redraw();

}

}

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

    • ההודעה area גורמת לחישוב שונה לכל צורה

  • במקום לשאול עצם מיהו ולהגיד לו מה לעשות בהתאם - פשוט שולחים לו את ההודעה והיא תפורש בהתאם לזהותו

  • קוד אשר משתמש בפולימורפיזם מחליף שימוש ב-if ו-switch בקריאות לפונקציות וירטואליות

    • קור קצר יותר ונקי יותר

    • קל להוסיף מקרים חדשים

הוספת Widget חדש אינה דורשת שינויים ב-Window

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


10

פולימורפיזם והעתקת עצמים

  • פולימורפיזם והעברה by-value (או העתקת עצמים בכלל) אינם משתלבים:

  • עצם המוחזק “by value” (כלומר לאכרפרנס או מצביע) לא יכול להיות בעלטיפוס שונה בזמן הריצה

  • שימוש ב-copy c’tor יוצר עצם מהטיפוס המוגדר לפי שמו

    • לכן העתקת Animal יוצרת Animal חדש בהתבסס על חלק ה-Animal של a

    • לא ניתן ליצור העתק Shape של s כי Shapeאבסטרקטית

  • כאשר משתמשים בהורשה עושים זאת עם רפרנסים ומצביעים

    • הדבר נכון גם להעברת והחזרת ערכים

voidfoo(Animal& a, Shape& s) {

Animal copy = Animal(a);

copy.makeSound();

a.makeSound();

Shape copy2 = Shape(s); // error

}

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


10

חריגות ופולימורפיזם

class Stack {// ...classException : publicstd::exception {};

classFull : publicException {};

classEmpty : publicException {};

};

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

  • ניתן לתפוס חריגות לפי מחלקתהאב שלהן וכך לאפשר תפיסהשל חריגות ספציפיות או חריגות מקבוצהכללית יותר

  • חשוב לתפוס חריגות עם &, למה?

  • הספריה הסטנדרטית מגדירה מספר חריגותשלכולן אב משותף - std::exception

    • מומלץ שחריגות יירשו את exception(בעקיפין או ישירות)

voidf() {

try {

Stack s(100);

do_stuff(s);

} catch (Stack::Full& e) {

cerr << "Not enough room";

} catch (Stack::Exception& e) {

cerr << "Error with stack";

} catch (std::exception& e) {

cerr << e.what() << endl;

}

}

what היא פונקציה וירטואלית המוגדרת ב-std::exception

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


10

חריגות ופולימורפיזם

classAException {};

classBException:

publicAException {};

classCException:

publicAException{};

  • אם מספר כללי catch מתאימים לתפיסת חריגה ייבחר הכלל אשר מופיע ראשון

    • לכן מתחילים מהמקרה הספציפי ביותרעד לכללי ביותר (לכן שימוש ב-“...” תמיד יופיע אחרון)

  • מה יודפס בכל אחת מהפונקציות?

voidh() {

try {

throwAException();

} catch (BException& b) {

cout << "B caught";

} catch (CException& c) {

cout << "C caught";

} catch (AException& a) {

cout << "A caught";

}

}

voidf() {

try {

throwCException();

} catch (BException& b) {

cout << "B caught";

} catch (CException& c) {

cout << "C caught";

} catch (AException& a) {

cout << "A caught";

}

}

voidg() {

try {

throwCException();

} catch (BException& b) {

cout << "B caught";

} catch (AException& a) {

cout << "A caught";

} catch (CException& c) {

cout << "C caught";

}

}

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


10

שימוש נכון בהורשה

  • כאשר יוצרים מחלקה B היורשת את A חשוב להקפיד:בכל מקום שבו ניתן להשתמש ב-A יהיה ניתן להשתמש ב-B

  • נניח שנרצה להוסיף מחלקה עבוראליפסה

    • האם עיגול הוא סוג של אליפסה?

    • לא, בגלל המתודה setAB עיגול לא יכוללעשות את כל מה שאליפסה עושה

  • המחלקה היורשת צריכה להוסיףעלהתנהגות מחלקת האב

    • לא להסתיר

    • לא לשנות

  • במקרה שלנו Ellipse ו-Circle צריכות לרשתישירות את Shape

classEllipse : publicShape {inta,b;public:

Ellipse(int x, int y, int a, int b);

virtualdoublearea() const;

voidsetAB(int a, int b);};classCircle : publicEllipse{public:Circle(int x, int y, int radius) :

Ellipse(x,y,r,r) {}};

voidbreak_stuff(Ellipse& e) {

e.setAB(1,2);}

e יכול להיות Circle!

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


10

פולימורפיזם - סיכום

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

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

  • ניתן להשתמש בפולימורפיזם כדי להעלים if ו-switch מהקוד ולהחליף בקוד פשוט יותר וקל יותר להרחבה

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

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

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

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


Typeid

המרות ו-typeid

המרות ב-C++

typeid

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


10

המרות ב-++C

  • ניתן ב-C++ להמיר עם תחביר של C או תחביר של בנאי:

    (Type)varאוType(var);

    • שתי המרות אלו שקולות

  • להמרות אלו חסרונותרבים:

    • המרות מסוג זה הן ערבוב של משמעויות:

      • המרה בין מצביעים עם קשר הורשה ביניהם מוגדרת, אך המרה בין מצביעים שאין קשר ביניהם אינה מוגדרת

      • ניתן בטעות להמיר עצם קבוע (const) ללא קבוע - התוצאה יכולה להיות לא מוגדרת

    • קשה לדעת איזו המרה התבצעה- המרות בין מצביעים מתקמפלות תמיד, אך משמעותן עלולה להשתנות בגלל שינויים ללא התראות מהקומפיילר

    • קשה לזהות את ההמרות בקוד או לחפשאותן

  • לשם כך מוגדרות ב-C++ המרות מיוחדות הפותרות את בעיות אלו

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


10

המרות

  • ב-C++ קיימים ארבעה סוגי המרות:

    • static_cast: ממירה בין שני עצמים בעזרת בנאי או בין מצביעים/רפרנסים שיש ביניהם קשר של הורשה

      • נכונות ההמרה נבדקת בזמן הקומפילציה

      • בהמרה ממחלקת אב למחלקת בן המשתמש אחראי על הנכונות

    • dynamic_cast: ממירה בין מצביעים/רפרנסים

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

    • const_cast: מסירה const מהטיפוס

    • reinterpret_cast: ממירה בין של שני טיפוסים על ידי פירוש מחדש של הביטים

      • משמשת למשחקי ביטים ודיבוג

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


Static cast

המרות - static_cast

voidf(int& n, A& base) {

double d = static_cast<double>(n);

B& derived = static_cast<B&>(base);

B* ptr = static_cast<B*>(&base);

double* ptr2 = static_cast<double*>(&n);

C& unrelated = static_cast<C&>(base);}

  • המרה סטטית מאפשרת המרה ביןעצמים בעזרת המרה מוגדרת(ע"י בנאי או אופרטור המרה)

    • במקרה שאין המרה מוגדרת תתקבלשגיאת קומפילציה (בדומה להמרהרגילה)

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

    • אם אין קשר בין הטיפוסים בהמרה סטטית תתקבל שגיאת קומפילציה

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

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


Dynamic cast

המרות - dynamic_cast

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

  • בהמרה דינאמית של מצביעיםיוחזר NULL אם ההמרה אינה אפשרית

  • בהמרה דינאמית של רפרנסיםתיזרק std::bad_cast אם ההמרה אינה אפשרית

  • המרה דינאמית עובדת רק על טיפוסים פולימורפיים (בעלי פונקציה וירטואלית אחת לפחות)

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

voidg(int n, A& base, C& c) {

B* ptr = dynamic_cast<B*>(&base);

if (ptr == NULL) {

cerr << "base is not a B";

}

try{

B& derived = dynamic_cast<B&>(base);

} catch (std::bad_cast& e) {

cerr << "base is not a B";

}

double* ptr2 = dynamic_cast<double*>(&n);

A& unrelated = dynamic_cast<A&>(c);}

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


Typeid1

typeid

#include<typeinfo>

//...

classBase {

virtualvoidv() {}

};

classDerived : publicBase{};voidf() {

Base* ptrb = new Base;

Base* ptrd= new Derived;

cout << typeid(ptrb).name() << endl;

cout << typeid(ptrd).name() << endl;

cout << typeid(*ptrb).name() << endl;

cout << typeid(*ptrd).name() << endl;

if (typeid(*ptrd) != typeid(Base)) {

cout << "diff";

}

return 0;

}

  • האופרטור typeidמקבל שם טיפוס או ערך ומחזיר עצם מטיפוס type_info המתאר את שם הטיפוס

    • ניתן להשוות type_info

    • ניתן לקבל מחרוזת המתארת את שם טיפוס (טוב למטרות דיבוג)

  • מומלץ להימנע משימוש ב-typeid

    • שימוש בשאלה "איזה טיפוס אתה?" ובחירה בקוד לפיו מחזירה את החסרונות של קוד ללא פולימורפיזם

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


Typeid2

המרות ו-typeid - סיכום

  • המנעומשימוש בהמרות ו-typeid - הם גורמים לקוד שקל להכניס בו באגים וקשה לשנות אותו

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

  • אם חייבים לבצע המרה השתמשו ב-static_cast

  • אם חייבים להמיר ממחלקת אב לבן השתמשו ב-dynamic_cast

  • המנעומשימוש ב-typeid או מימוש מנגנון דומה בעצמכם

  • המנעומהמרות בסגנון של C

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


10

העשרה - מימוש הורשה

מימוש יחס “is a”

מימוש פונקציות וירטואליות

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


10

העשרה - מימוש הורשה

class A {

inta;

intb;

public:

intf();

intg();

};

classB : publicA {

intc;

intd;

public:

inth();

};

  • כיצד יכול הקומפיילר לממש את ההתנהגות של “B is an A” ?

  • מבנה העצם בזיכרון ייראה כך:

    • תחילה השדות של מחלקת האב

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

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

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

עצם מסוג A

A

a

b

מי שמסתכל על תחילת העצם רואה A

בכל מקרה

מי שמסתכל על תחילת העצם רואה A

בכל מקרה

עצם מסוג B

A

B

a

b

c

d

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


10

העשרה - פונקציות וירטואליות

class A {

inta;

intb;

public:

virtualintf();

virtualintg();};

classB : publicA {

intc;

intd;

public:

virtualintf();

};

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

  • לכל מחלקה נשמרת טבלה (הקרויה vtbl) עם מצביעים עבור הפונקציות הוירטואליות המתאימות ביותר לטיפוס

  • התא הראשון של כל עצם יכיל מצביע (הקרוי vptr) אשר יצביע לטבלה המתאימה ביותר לעצם

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

vtbl for B

B::f

vtbl for A

עצם מסוג A

A::g

A::f

A

A::g

vptr

a

b

עצם מסוג B

A

B

voidcall_f(A* constthis) {

this->vptr[0](this);

}

vptr

a

b

c

d

להמחשה בלבד

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


  • Login