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

תרגול מס
Download
1 / 38

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

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

I am the owner, or an agent authorized to act on behalf of the owner, of the copyrighted work described.

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