לנץ קרן מרצה: תכנותמונחהעצמים) 027622 ( יוםשישי 15 אוקטובר 0202 ב מועד 0202, אביב סמסטר סמסטר סוף מבחן גוטמן אייל רביב, אריאל משנה, אלון מתרגלים: הנחיות:.1.2.3.4.5.6.7 השאלות. כל על לענות עליכם משקל. שוות שאלות 5 במבחן 20% ותקבלו יודע/ת" "לא כתבו התשובה, את יודעים אינכם אם שאלה, בכל מהנקודות. על הניתן בציון תפגע נכון( הוא אם )אף בעליל רלוונטי לא מידע הוספת לעניין. ענו שלילי. ניקוד למתן אף קיצוניים ובמקרים התשובה, ברור. בכתב תשובתכם כתבו חדש. בעמוד תשובה כל התחילו עזר. חומר בכל להשתמש אין כולם. את שקיבלתם כעת וודאו ) זה עמוד כולל ( עמודים 5 במבחן.8 הארכה. תינתן לא שעות. שלוש הבחינה משך 1 326732 עצמים מונחה תכנות 3313 אביב
שאלה 1 מנגנון הפעלת מתודות ב-++ C למדנו שב ++C מנגנון הרשאות הגישה protected( private, וכו'( נבדק רק בזמן קומפילציה. א. תנו דוגמה לקטע קוד ב ++C שיעבור קומפילציה וריצה, אבל מהווה הפרה של הרשאות גישה )לדוגמה, קריאה למתודה שמסומנת כ private מתוך מחלקה אחרת(. class A { public: virtual void f(); ; class B : public A { private: virtual void f(); ; A *p = new B(); p->f(); // calls the private B::f א. ב. האם תתכן הפרה כזו גם בדוגמת קוד שלא כוללת מתודות וירטואליות? אם כן תנו דוגמה, אחרת הסבירו מדוע לא. כאשר לא מדובר במתודות וירטואליות הקישור בין שם המתודה לגוף המתודה נעשה בזמן קומפילציה וכך גם מנגנון הרשאות הגישה, ולכן הדוגמא מהסעיף הקודם לא תקפה. הציעו דרך לשנות את מנגנון הפעלת המתודות ב ++C כך שהרשאות הגישה ייבדקו גם בזמן ריצה כלומר שקריאה למתודה החורגת מהרשאות הגישה שלה תגרור שגיאה. בתשובתכם התייחסו ל: a. השינויים הנדרשים במבני הנתונים שמעורבים בתהליך. b. השינויים באלגוריתם הפעלת מתודות. הערות: A ניתן לבדוק האם B, ו- A ניתן להניח כי בהינתן טבלאות וירטואליות של מחלקות היא תת טיפוס של B. ניתן להתעלם ממנגנון החברים )friend( בשפה. ג. 3
נוסיף עמודה ב- virtual table של כל מחלקה, המציינת את הרשאת הגישה של כל מתודה ווירטואלית במחלקה. בעת קריאה למתודה ווירטואלית, נבדוק את הרשאת הגישה, וכן את היחס בין מחלקת האובייקט ממנו מתבצעת הקריאה,this( קונטקסט(, לבין מחלקת האובייקט עליו מתבצעת הקריאה )הפרמטר הראשון למתודה,.)receiver גישה private תאופשר רק אם המחלקות זהות, או שמחלקת הקונטקסט חברה של מחלקת ה ;receiver גישה protected תאופשר באותם תנאים, או אם מחלקת הקונטקסט הינה מחלקת בת של מחלקת ה ;receiver גישה public תאופשר תמיד. במקרה של גישה לא חוקית, תזרק חריגה מתאימה. ניתן לציין כי מתודות שאינן ווירטואליות, יבדקו בזמן קומפילציה, כרגיל. ד. האם הבעיה מסעיף א' קיימת גם בשפת?Java נמקו. אותה בעיה לא, מכיוון שלא ניתן לצמצם הרשאות גישה בעת ירושה. אולם מנגנון הרשאות הגישה בג'אווה אף הוא סטטי, ולכן ניתן ליצור תסריטים בעייתיים, למשל: class A { protected void f(){... class B : public A { protected void f(){...// override class C : public A { void g() { A p = new B(); p.f(); // calls B::f, even though C is not a descendant of B 2
שאלה 2 Squeak ו- Beta Refinement תזכורת ב,beta refinement בעת קריאה למתודה, המתודה השייכת למחלקה הגבוהה ביותר בהירארכיה היא זו שנקראת, ויש דרך לקרוא מתוך מתודה נדרסת למתודה דורסת. מכאן, הקריאה למתודה הדורסת היא בעצם באחריות המתודה הנדרסת. א. רוצים לשנות את Squeak כך שהיא תתמוך ב.beta refinement הציעו כיצד יש לשנות את מנגנון ה dynamic dispatch של Squeak על מנת לתמוך בשינוי זה. ניתן להניח כי נוספה לשפה מילה שמורה חדשה,,inner בעלת הסמנטיקה המקובלת בשפות המממשות,beta refinement כלומר הופעה של מילה זו בקוד תגרום לקריאה למתודה בעלת אותה חתימה, עם הארגומנטים של המתודה הנוכחית, אבל במחלקה הבאה בתור לפי הסדר המוגדר בעידון מסוג.beta אין לשנות את מודל האוביקט של השפה. אין צורך לכתוב קוד או להתייחס למחלקות ספציפיות, צריך רק לתאר בכלליות את תהליך ה- dispatch החדש. תהליך ה dispatch החדש יעלה בהיררכיית הירושה של המחלקה מהמחלקה של ה reciever ועד למחלקה ProtoObject ויחפש את המחלקה הגבוהה ביותר שהדירה את הפונקציה, תוך כדי שמירת סדר המחלקות בהן ביקר. אם לא נמצאה כזו, תופעל המתודה.doesNotUnderstand כאשר נתקל במילה,inner נתחיל את החיפוש שוב, אך הפעם מהמחלקה הגבוהה ביותר בסדר יורד )הפוך מהסדר שנשמר(. כאשר נמצא את המתודה נפעיל אותה. ייתכן שהתהליך יחזור על עצמו. ב. האם השינוי שהצעתם מאפשר גם beta refinement עבור מתודות מחלקה )כלומר מתודות שהוגדרו במטה-מחלקה וה receiver שלהם זה מחלקה(? כן, מודל 5 הרמות של Squeak מאפשר להפעיל את המנגנון גם עבור מתודות מחלקה. השינוי היחיד הוא שהחיפוש ייערך במטה-מחלקות במקום במחלקות. ג. האם ניתן לשנות את Java כך שתתמוך ב- refinement :beta עבור מתודות סטטיות? a. עבור מתודות שאינן סטטיות? b. עבור מתודות סטטיות השינוי אינו אפשר, כיוון ש Java שפת 3 רמות בה הקישור למתודות מחלקה הוא סטטי, ויש רק מטה מחלקה אחת..a 4
עבור מתודות שאינו סטטיות, ניתן לממש את השינוי ע"י שינוי במבנה הטבלה הוירטואלית, כך שתשמור גם מצביע למתודה הקדומה ביותר שהגדירה את המתודה, או להשתמש במנגנונים של.reflection.b שאלה 3 תאימות לפניך הקוד הבא המכיל 4 מחלקות בשפת Java המתארות כלי רכב ונהגים המתאימים להם: public class Driver { public final String name; Driver(String name){ this.name = name; public class Car { protected Driver driver; public Driver getdriver() { return driver; public void setdriver(driver d) { this.driver = d; public class Trucker extends Driver { Trucker(String name) { super(name); public class Truck extends Car { public Trucker getdriver() { return (Trucker)driver; public void setdriver(trucker t) { this.driver = t; א. האם אפשרי שנהג רגיל (Driver) ינהג במשאית?(Truck) אם כן, רשמו קוד קצר המדגים זאת. אם לא, הסבירו מדוע הדבר אינו אפשרי. כן. הדבר אפשרי בגלל מנגנון ה overloading ב- Java Car c = new Truck(); Driver d = new Driver("Bob ); c.setdriver(d); ב. כעת מוסיפים את השדה protected Trucker driver למחלקה.Truck האם הקוד הבא ירוץ ללא שגיאות, תהיה שגיאת קומפילציה או שגיאת זמן ריצה? נמקו. Car c = new Truck(); Trucker tr = new Trucker("Jim"); c.setdriver(tr); System.out.println(c.getDriver().name); שגיאת זמן ריצה.(null-pointer( המתודה Set משנה את השדה Driver ואילו המתודה Get מתייחסת לשדה Trucker בגלל Co-variance של ערך ההחזרה. 5
ג. הקוד הבא מתאר מתודה המקבלת מערך של נהגים ומערך ריק של כלי רכב וממלאת את מערך כלי הרכב כך שכל נהג יקבל כלי רכב המתאים לו. public static void assign(car[] cars, Driver[] drivers) { for (int i = 0; i < drivers.length ; i++) { if (drivers[i] instanceof Trucker) { cars[i] = new Truck(); else if (drivers[i] instanceof Driver) { cars[i] = new Car(); cars[i].setdriver(drivers[i]); { בהנחה שהמערך drivers תקני )אינו מכיל ערכי null ובאורך שווה למערך,)cars האם השימוש במתודה עלול להוביל לשגיאת זמן ריצה? אם כן, רשמו קוד קצר המדגים זאת. אחרת, הסבירו מדוע הדבר אינו אפשרי. Car[] cars = new Truck[1]; Driver[] drivers = {new Driver("Bob"); assign(cars,drivers) 6
struct A { int i; ; שאלה 4 מבנ ה אובייקט, ירושה מרובה נתונות המחלקות הבאות: template <typename T> struct B : public A { virtual void f(t t) { ; struct C : public B<char*>, public B<double> { ; א. שרטטו את מבנה האוביקט C תוך התייחסות לתוכן הטבלאות הוירטואליות. B<char*> A C B<double > A vptr A::i vptr A::i B<char*>::f(char*) B<double>::f(double) ב. האם הקוד הבא יתקמפל וירוץ בהצלחה? נמקו מדוע כן או מדוע לא. התייחסו בתשובתכם בנפרד לקומפילציה ולריצה. int main() { C* c = new C(); c->f(0.5); return 0; הקוד לא יתקמפל בהצלחה, כי ++C תומך ב overloading רק באותה המחלקה )על אותה ההיררכיה(, ולא בין מחלקות שונות עם יחס ירושה ביניהן. מכאן שנכון ש C יורש גם את f(char*) וגם,f(double) אבל אין אפשרות לעשות ביניהן overloading ולכן חייבים לציין במפורש לאיזו f הכוונה, לדוגמה ע"י.upcasting 7
ג. לפניכם קוד )כמעט( מקביל ב.Java האם הקוד הזה יתקמפל וירוץ בהצלחה? נמקו מדוע כן או מדוע לא, ושוב התייחסו בתשובתכם בנפרד לקומפילציה וריצה. public class Test{ public static void main(string[] args) { C c = new C(); c.f(0.5); interface A { static final int i = 3; interface B<T> extends A { void f(t t); static class C implements B<String>, B<Double> { public void f(string s) { public void f(double d) { הקוד לא יתקמפל בהצלחה, כי בגלל ה erasure אין אפשרות ב Java לרשת מאותו ה interface פעמיים עם פרמטר גנרי שונה בכל פעם, כי הרי זו בעצם אותו הממשק. 8
שאלה 5 דריסת מתודות ב #C א. נתונה היררכית המחלקות הבאה: class X { public int f() { return 1; class Y : X { public int f() { return 2; class Z : Y { public int f() { return 3; בנוסף, נתון קטע הקוד הבא: Z z = new Z(); Y y = z; X x = y; Console.WriteLine(x.f() + ">" + y.f() + ">" + z.f()); שנו את החתימות )בלבד( של המתודות f במחלקות האלו כך שקטע הקוד יתקמפל וידפיס 3<2<2, או הסבירו מדוע לא ניתן לעשות זאת. שנו את החתימות )בלבד( של המתודות f במחלקות האלו כך שקטע הקוד יתקמפל וידפיס 1<2<1, או הסבירו מדוע לא ניתן לעשות זאת..a.b X.f: virtual Y.f: override Z.f: new לא ניתן לעשות זאת, כדי שהמספר השני יהיה 2 המתודה Y.f חייבת להיות מסומנת כ new )או ללא כל,)modifier וזה אומר ששום מתודה באותו שם במורד ההירככיה לא תוכל לדרוס את f של X..a.b ב. נניח שיש לנו שתי תוכניות זהות לחלוטין פרט למחלקות A ו- B הבאות, שממומשות באופן שונה בשתי התוכניות: תוכנית 2 תוכנית 1 class A { public int f() { return 1; class A { public virtual int f() { return 1; class B : A { public int f() { return 2; class B : A { public new int f() { return 2; 9
ספקו דוגמת קוד ב #C שמתקמפלת בהצלחה גם תחת תוכנית 1 וגם תחת תוכנית 2, אבל מתנהגת שונה בכל אחת מהתוכניות )לדוגמה, הראו מתודה שמחזירה ערך שונה( או לחלופין, הסבירו מדוע לא ניתן לספק דוגמה כזו. לא ניתן לתת דוגמה כזו. בשתי התוכנות B.f מסתירה את.A.f לא ניתן להוסיף מחלקה שיורשת מ A )ולא מ B( ודורסת את f, כי כדי לדרוס חייב להיות כתוב שם,override אבל זה לא יתקמפל תחת תוכנה 2 כי override יכול להיות כתוב רק אם המתודה באב מסומנת כ.virtual 13