יום שלישי, 14 ליולי 2015 מבחן סוף סמסטר - תכנות מונחה עצמים )236703( סמסטר, מועד א' מרצה: ערן גלעד מתרגלים: נורית מושקוביץ', הלאל עאסי, אלירן וייס הנחיות: 1. במבחן 5 שאלות שוות משקל. עליכם לענות על כל השאלות. 2. בכל סעיף, אם אינכם יודעים את התשובה כתבו "לא יודע/ת" ותקבלו 20% מהנקודות. 3. בכל שאלה בה נדרש לציין את פלט הקוד, אם לא נאמר במפורש אחרת ייתכן כי הקוד לא מתקמפל או רץ. במקרה כזה יש לציין את שגיאת הקומפילציה או זמן הריצה ולהסביר מדוע היא מתרחשת. 4. בקורס הוצגו מספר מימושים ספציפיים )קישור דינמי, מבנה האובייקט, mixin וכו'(, אשר קיימות להם חלופות. בשאלות הרלוונטיות, ניתן לתאר כל אחד מהמימושים שנלמדו, להוציא אלה שהוצגו כדוגמא לפתרונות גרועים. 5. ענו לעניין. הוספת מידע לא רלוונטי בעליל )אף אם הוא נכון( תפגע בציון הניתן על התשובה. 6. עם זאת, הסבר נכון חלקית לתשובה שגויה עשוי לקבל ניקוד חלקי. מומלץ להסביר את תשובותיכם גם אם לא התבקשתם במפורש, אך אנא הימנעו מהרחבה מיותרת. 7. כתבו תשובתכם בכתב ברור. אי אפשר לבדוק תשובות בלתי קריאות. 8. התחילו כל תשובה בעמוד חדש. 9. אין להשתמש בכל חומר עזר. 10. במבחן 11 עמודים )כולל עמוד זה(. ודאו כעת שקיבלתם את כולם. 11. משך הבחינה שלוש שעות. בהצלחה! עמוד 1 מתוך 11
שאלה 1: ירושה מרובה ב-++ C struct A { int x; A() : x(1) { cout << "A" << endl; A(int i) : x(2) { cout << "A int " << i << endl; struct B : virtual public A { int x; B() : A(), x(3) { cout << "B" << endl; B(int i) : A(), x(4) { cout << "B int " << i << endl; struct C1 : public B { int x; C1(int i) : B(i), x(5) { cout << "C1 " << endl; virtual void foo() { cout << "foo " << x << endl; struct C2 : virtual public B { int x; C2() : B(), x(6) { cout << "C2" << endl; virtual void bar() { cout << "bar " << x << endl; struct D : public C1, public C2 { int x; D(): C1(7), C2(), x(8) { cout << "D" << endl; D* d = new D(); C1* c1 = d; A* a = d; א. ב. מהו הפלט של התוכנית? הסבירו את ההבדלים בין מנגנוני ה- cast הבאים של ++C: static_cast dynamic_cast reinterpret_cast ג. עבור כל אחת מהשורות הבאות, ציינו האם השורה תתקמפל, ואם לא הסבירו מדוע. 1. static_cast<d*>(a); 2. dynamic_cast<d*>(a); 3. static_cast<b*>(d); 4. static_cast<b*>(reinterpret_cast<c2*>(d)); 5. dynamic_cast<a*>(d); המשך השאלה בעמוד הבא עמוד 2 מתוך 11
המשך שאלה 1 ד. האם הרצת השורות הבאות תצליח? אם כן, ציינו מה יהיה הפלט. אם לא, הסבירו מדוע. C2* c2 = dynamic_cast<c2*>(c1); c2->bar(); ה. נתונה הפונקציה הבאה: int offset(d* d) { return reinterpret_cast<int>(static_cast<a*>(d)) - reinterpret_cast<int>(d); הניחו כי אין שגיאה בהמרת המצביע ל- int. האם קריאה ל- offset תמיד תחזיר את אותו ערך? אם כן, מהו הערך? אם לא, הסבירו מדוע. עמוד 3 מתוך 11
Squeak שאלה 2: בשאלה זו רוצים להוסיף מנגנון חדש לשפת סקוויק, שיאפשר לקבל מכל מחלקה את מספר המופעים שנוצרו ממנה. לשם כך, מוסיפים למחלקה Behavior את המרכיבים הבאים: השדה,count אשר יחזיק את מספר המופעים שנוצרו המתודה,getInstancesNum אשר תחזיר את ערך השדה המתודה,initCount אשר תאתחל את שדה המונה לאפס לנוחיותכם, בעמוד הבא תמצאו שרטוט חלקי של מודל האובייקט של סקוויק. א. ב. ג. כיצד ניתן לחשב את מספר המחלקות )כולל מטה-מחלקות( שיש בתוכנית באמצעות המנגנון החדש? לשם הפשטות, התוצאה יכולה להיות מקורבת, ללא התייחסות למחלקות מיוחדות אשר נוצרות לפני איתחול המנגנון או שאינן חלק מהמודל שנלמד. כתבו ביטוי מתאים בסקוויק, והסבירו את תשובתכם. ניקוד מלא ינתן רק לפתרונות שלא ישתמשו ברקורסיה או בלולאות. הסבירו היכן יש לאתחל את שדה המונה ואיך יש לעשות זאת. לשם הפשטות, התייחסו אך ורק לאתחול המונה עבור מחלקות שאינן מטה-מחלקות. הסבירו היכן יש לעדכן את שדה המונה ואיך יש לעשות זאת. גם כאן, ניתן להתייחס אך ורק לעדכון המונה עבור מחלקות שאינן מטה-מחלקות. נטען כי פתרון זה אינו אופטימלי מבחינת סיבוכיות הזיכרון, מכיוון שניתן להחזיק את השדה רק בחלק מהמחלקות. תארו את השינויים הנדרשים על מנת להשיג מימוש אופטימלי: ד. ה. היכן יש להגדיר את השדה?count רשמו את כל המקומות האפשריים, והסבירו את תשובתכם. איזה שינויים נוספים יש לבצע עבור הפתרון היעיל? הסבירו. עמוד 4 מתוך 11
נספח לשאלה - 2 מודל האובייקט של סקוויק )חלקי( Behavior Behavior class ClassDescription ClassDescription class Class Class class nil ProtoObject ProtoObject class Object Object class Metaclass Metaclass class עמוד 5 מתוך 11
שאלה 3 חריגות ואנוטציות רוצים להוסיף ל-++ C יכולת דומה ל- finally של.Java נתון הקוד הבא 1. 2. int* arr = new int[10]; 3. 4. try { 5. 6. foo(arr); 7. 8. catch (const exception& e) { 9. 10. cout << D oh! << endl; 11. 12. 13. א. הגדירו מחלקה בשם Finally והשתמשו בה כדי לשחרר את הקצאת הזיכרון שב- arr בין אם ב- Java finally תזרוק חריגה ובין אם לא. כלומר, עליה לספק יכולות דומות לאלה של foo )ניתן להניח כי שחרור הזיכרון עצמו לא יזרוק חריגה(. פרטו את השינויים שיש לעשות בקוד למעלה. בגירסה העתידית 1k של Java רוצים להוריד את ההכרח להכריז על חריגות שעשויים לזרוק, ולהשתמש בכללים דומים לאלה שהיו בגירסאות קודמות של ++C: אפשר להצהיר על זריקה באמצעות,throws אבל לא חובה ניתן לתפוס חריגות, אבל לא חובה אם זורקים בניגוד למוצהר התוכנית מסתיימת מיידית, לפני החזרה של הפונקציה הזורקת )הניחו כי היעדר הצהרה מאפשר לזרוק כל סוג של חריגה, מבלי שהתוכנית תסתיים( האבחנה בין שני סוגי החריגות השונים ב- Java מבוטלת, ולכל החריגות מעמד זהה כדי לספק תאימות לאחור, רוצים לאפשר הרצה של תוכניות שמורכבות מ- files class שנכתבו בגירסה 1k יחד עם class files של גירסאות מוקדמות יותר. ב. נתונה המתודה,A.caller אשר קוראת למתודה.B.callee כל אחת מהמחלקות קומפלה בנפרד. קבעו האם בכל אחד מהמצבים הבאים תתכן בעיית תאימות. כלומר, האם הקוד של A.caller "יופתע" מהתנהגות.B.callee אם כן, תארו במדויק את הבעיה: המחלקה A קומפלה בגירסה 7 של,Java והמחלקה B בגירסה 1k המחלקה A קומפלה בגירסה 1k של,Java והמחלקה B בגירסה 7 תצפה שהריצה תיפסק, אבל החריגה תועבר אליה מ- B. A.1.2 המשך השאלה בעמוד הבא עמוד 6 מתוך 11
המשך שאלה 3 כזכור, לאנוטציות יש 3 סוגי,retention כלומר טווחי שימור:.Source, Class, Runtime רמת ה- retention קובעת האם האנוטציה נשמרת בקוד בלבד, ב- file class או עד )וכולל( זמן הריצה. ג. עבור כל אחד ממרכיבי מנגנון החריגות הבאים ב- Java, ציינו והסבירו מהו סוג ה- retention הנדרש. לדוגמה, אם מרכיב מסוים אמור להיות זמין בזמן ריצה, סוג ה- retention המתאים עבורו הוא Runtime )חריגות לא מבוססות על אנוטציות כמובן; השאלה רק מתבססת על טווחי השימור, שמוגדרים היטב באנוטציות(: מבנה ה- try וה- catch הזריקה של החריגה הצהרת ה- throws.1.2.3 ד. הגדרת ה- retention של אנוטציה מתבצעת באמצעות מטה-אנוטציה בשם.Retention עד מתי אמורה להישמר המטה-אנוטציה?Retention הסבירו. עמוד 7 מתוך 11
)++C מתקדם( שאלה 4 תרגיל בית 5 בתרגיל הבית ראינו כיצד ניתן לממש באופן חלקי את פונקצית ההמרה,static_cast הפועלת בזמן קומפליציה, ואת פונקצית ההמרה הדינמית dynamic_cast אשר בודקת טיפוסים בזמן ריצה. א. כזכור, רק באחד ממנגנוני ההמרה הללו ניתן לבצע downcast כאשר הטיפוס המקורי הינו אב וירטואלי של טיפוס היעד, בעוד שבמנגנון השני הדבר לא אפשרי. באיזה מהמנגנונים המרה כזו תתכן? הסבירו מדוע. הסטודנט יוסי שהגיע להרצאות שמע שעלולה להיווצר דו משמעות בהמרה למטה במקרה של גרף ירושה מורכב. לרשותו פונקציה בשם,can_cast() המקבלת אובייקט מטיפוס פולימורפי כלשהו ומחזירה true אם אין ירושת מרובה מאותו אב בשרשרת ההורשה של הטיפוס הדינמי, ו- false אחרת. כמו כן, הוא כתב את הפונקציה הבאה להמרה בין טיפוסים: בנוסף, נתונות המחלקות הבאות: template<typename Dst, typename Src> Dst do_cast(src src) { if (!can_cast(src)) throw std::bad_cast(); return (Dst)src; class A { public: virtual ~A() { class B : public A { public: virtual ~B() { class C : public A { public: virtual ~C() { class D : public B, public C { public: virtual ~D() { וקטע הקוד מתוך התוכנית הראשית של יוסי: try { do_cast<d>(a()); catch (const std::bad_cast& e) { cout << "bad cast!" <<endl; catch (...) { cout << "unexpected error!" << endl; ב. האם קטע הקוד הנ"ל יתקמפל? אם כן, כתבו מה יודפס והסבירו מדוע. אם לא, הסבירו מדוע והציעו תיקון לקוד )במילים או בקוד( בכדי שהוא יתקמפל ויפעל כמצופה. סטודנט נוסף בשם גיל הבין כי נדרשת גרסה מיוחדת של do_cast עבור מצביעים, שתחזיר NULL במקום לזרוק שגיאה. בכדי לתמוך בהתנהגות זו, גיל חשב להשתמש במנגנון ה- partial specialization בשפה. ג. הסבירו כיצד ניתן להשתמש במנגנון ה- partial specialization כדי לממש את השינוי שגיל מעוניין לבצע, תנו דוגמת קוד במידת הצורך. ניתן לבצע שינויים בקוד הקיים, אך יש לציין כיצד להשתמש בפונקציית ההמרה במנגנון החדש שלכם. המשך שאלה בעמוד הבא עמוד 8 מתוך 11
המשך שאלה 4 ד. בהמשך, גיל החליט שהוא לא מעוניין להשתמש ב- cast C-Style עבור ההמרה עצמה במנגנון ההמרה הדינמית, והחליט שהוא רוצה לממש את ההמרה בעצמו. איזה מידע נוסף יש לשמור לכל מחלקה כדי לתמוך בביצוע המרה בזמן ריצה? בכדי לבדוק שכל האובייקטים שמשתמשים בהמרה שומרים את המידע הנוסף, גיל החליט שכל מחלקה שמשתמשת במידע הנוסף תממש פונקציה בשם hasspecialfeature() void אשר תשמש כאינדיקציה בלבד: הפונקציה יכולה להיות ריקה לגמרי. הוא החליט לממש את הבדיקה באופן הבא: template<typename T> class FeatureChecker { public: T& obj; FeatureChecker(T& t) : obj(t) { private: void CheckFeature() { obj.hasspecialfeature(); בכוונתו של גיל להשתמש בפונקציה באופן הבא: MyClass cls; FeatureChecker<MyClass> chkr(cls); המטרה היא שאם הטיפוס לא יעמוד בדרישה, הקוד למעלה ייכשל בזמן קומפילציה. ה. האם הקוד של גיל יעמוד במטרה? אם לא, הסבירו מדוע ותקנו את הקוד בהתאם. עמוד 9 מתוך 11
שאלה 5 Reflection Java, ומנגנונים מתקדמים שאלה זו הופיעה בסמסטר אביב 2012, מועד א' הקדמה: כדי שמחלקה בג'אווה תתמוך ב- clone, עליה לקיים את הכללים הבאים המחלקה תממש את הממשק,Cloneable שהוא ממשק ללא מתודות. המחלקה תדרוס את המתודה,clone() כך שתהיה מוגדרת כ- public ושהערך המוחזר יהיה מטיפוס המחלקה. המחלקה תקרא, ישירות או בעקיפין, ל-,Object.clone() על מנת לקבל מופע חדש של המחלקה. מחלקה שרוצה למנוע שימוש ב- clone )למשל במקרה שירשה ממחלקה המאפשרת )clone תדרוס את המתודה ותזרוק.CloneNotSupportedException סעיפי השאלה: א. נתונה המתודה הבאה, שמתיימרת לדמות את.Object.clone אילו מהכללים היא מפרה? public static Object myclone(object toclone) throws CloneNotSupportedException { try { Class<?> cls = toclone.getclass(); Object clone = cls.newinstance(); for (Field field : cls.getdeclaredfields()) { field.setaccessible(true); field.set(clone, field.get(toclone)); return clone; catch (Exception e) { throw new CloneNotSupportedException(); ב. הניחו שהבעיות מהסעיף הקודם תוקנו )אם קיימות(. האם אפשר לשנות את המתודה כך שתבצע deep copy לכל שדות האובייקט? )התעלמו מהאפשרות של מעגלי הצבעות( המשך השאלה בעמוד הבא עמוד 10 מתוך 11
המשך שאלה 5 עבור סעיפים ג' ו-ד' נתונה המחלקה הבאה, שדורסת את clone שמוגדרת ב- Object : class CanBeCloned implements Cloneable { public CanBeCloned clone() { try { return (CanBeCloned)super.clone(); catch (CloneNotSupportedException ex) { return null; // never happen we support cloning! { ג. המחלקה חוקית מבחינת השפה. עבור כל אחד מתת-הסעיפים הבאים, ציינו האם יישומו במחלקה מפר את כללי התאימות: הגדרת המתודה clone כ- public )ב- Object היא מוגדרת כ- protected ( הגדרת המתודה כך שתחזיר CanBeCloned ולא Object ביטול ההצהרה על זריקת,CloneNotSupportedException שהיא checked exception.1.2.3 ד. עבור כל אחד מתת-הסעיפים הבאים, ציינו האם הוא עלול לפגוע במימוש של מחלקות שירשו מ- :CanBeCloned הגדרת המתודה clone כ- public )ב- Object היא מוגדרת כ- protected ( הגדרת המתודה כך שתחזיר CanBeCloned ולא Object ביטול ההצהרה על זריקת,CloneNotSupportedException שהיא checked exception.1.2.3 עבור סעיפים ה' ו-ו' נתון הקוד הבא )הניחו שהמתודה שבה הוא נמצא מצהירה על זריקת :)CloneNotSupportedException Cloneable[] cloneables = getcanbeclonedarray(); // no problem here! for (Cloneable c : cloneables) { Object cclone = c.clone(); { ה. ו. מדוע הקוד הנתון לא עובר קומפילציה? האופן שבו Java מאפשרת תמיכה ב- clone נחשב ללא מוצלח. ב- Squeak, קיים ב- Object מימוש דומה, אך הוא איננו נחשב לבעייתי. מדוע? עמוד 11 מתוך 11