שאלה ב C++

הפרבולה

New member
שאלה ב C++

נתון הקוד הבא:

class A
{
public:
virtual int get() { return 111;}
};
class A1
{
public:
virtual int momo() { return 222;}
};
class B : public A1, public A
{
public:
int get() { return 333;}
};
void *makeB()
{
return new B();
}

main()
{
A *p1, *p2;
p1 = new B();
p2 = (A*)makeB();
int a1= p1->get(); // = 333
int a2= p2->get(); // = 222 ???????????

}
למה a2 מקבל את הערך 222 ?
הרי לא קראתי בשום מקום לפונקציה momo .
 

אוסנתס

New member
יצא לי הפוך בהודעה הקודמת

קוד:
p2 = (A)makeB();
int a2= p2->get();  // = ???????????
מה זה מחזיר (יצא לי הפוך בהודעה הקודמת עוד נסיון)
 

הפרבולה

New member
הכוונה ש a2 מקבל את הערך 222

שזה מה שהפונקציה momo מחזירה. ובאמת לפי הדיבגר היא נקראת שמתבצע
p2->get .
הנה הקוד שוב

קוד:
class A
{
   public:
   virtual int get() { return 111;}
};
class A1
{
    public:
    virtual int momo() { return 222;}
};
class B : public A1, public A
{
   public:
   int get() { return 333;}
};
void *makeB()
{
   return new B();
}
 
main()
{
  A *p1, *p2;
  p1 = new B();
  p2 = (A*)makeB();
  int a1= p1->get();  // = 333
  int a2= p2->get();  // = 222 ???????????
 
}
 

BravoMan

Active member
אתה משחק עם זיכרון כמו ב-C, ומקבל תוצאות בהתאם.

יש לך מחלקה B, שיורשת מתודות משתי מחלקות לא קשורות: A ו-A1.
&nbsp
זה אומר, שלאובייקט מטיפוס B יהיה VTABLE שיכיל 2 מצביעים: אחד למתודה get שהוא יורש מ-A ואחד ל-momo שהוא יורש מ-A1.
שים לב גם, שרשמת את A1 ראשון ברשימת הירושה, לכן הקומפיילר שם את המצביע ל-momo ראשון ב-VTABLE.
&nbsp
ב-makeB, אתה יוצר אובייקט מסוג B, ואז מחזיר מצביע אליו תוך שאתה עושה לו implicit cast ל-void, ואז עושה לו c-style cast ל-A.
&nbsp
מכאן, לתוכנה שלך אין דרך לדעת מה באמת יש בכתובת הזיכרון הזו, והיא מתייחסת לתוכן כאובייקט מטיפוס A כי זה מה שאמרת לה.
ל-A אמורה להיות רק רשומה אחת ב-VTABLE, אז כשאתה מפעיל get הקומפיילר לוקח את הרשומה הראשונה. שהיא כמובן מצביע ל-momo.
&nbsp
אם המחלקות שלך היו יותר מורכבות, היית מקבל ברדק גדול הרבה יותר, כי אתה למעשה זורק לפח את מנגנון הפולימורפיזם, ומבקש מהמחשב לפרש בצורה קבוע אזור מסוים בזיכרון, בלי שמה שנמצא באותו אזור תואם לדרך בה אתה דורש לפרש אותו.
&nbsp
אם היית עושה cast ל-*B, הקוד שלך היה עובד כמצופה.
 

הפרבולה

New member
תודה על ההסבר

אגב גם אם הייתי עושה CAST ל A ( במקום ל VOID בפונקציה MAKEB) זה היה עובד , ככה
קוד:
A *makeB()
{  A *pp=new B();
   return pp;
}
וגם ככה היה עובד שבתוך הפונקציה הטיפוס PP היה מוגדר כ A ולא B :
קוד:
void *makeB()
{  A *pp=new B();
   return pp;
}


בד"כ הייתי רוצה שהמצביעים P1 P2 יהיו מטיפוס של מחלקת האב , הבעיה שכאן יש למחלקה B יותר מאב אחד ( A ו A1 ) ולא ברור לי איזה אבא לבחור כטיפוס של המצביעים P1 P2 כדי שהכל יעבוד נכון ,ואולי הם חייבים להיות מטיפוס B במקרה של אבות מרובים ?

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

BravoMan

Active member
השאלה היא מה בדיוק אתה מנסה לעשות?

++C היא שפה מאוד "שמנה" - יש לה הרבה סינטקס שנותן הרבה יכולות, אבל זה גם משאיר הרבה חבל למתכנתים לתלות את עצמם.
ירושה מרובה היא דוגמה טובה לכך ולכן אין הרבה שפות מודרניות שתומכות בה.
&nbsp
מצד שני, היא יכולה להיות כלי חזק אם משתמשים בה נכון.
&nbsp
אז, כדי לייעץ לך איך נכון לממש את makeB, צריך להבין מה בדיוק אתה מנסה לעשות.
&nbsp
למה לך פונקציה שמייצרת מחלקות?
האם אתה מנסה לממש Factoyr pattern כלשהו?
&nbsp
לגבי מצביעים:
ההמלצה לכל מפתחי ++C היא להימנע ככל שניתן משימוש במצביעים חשופים סטייל C.
לא רק cast שלהם, אלא כל שימוש שהו.
בשביל זה ++C נותן לך referenceים ומצביעים חכמים למיניהם.
&nbsp
כמובן, גם בשפת C צריך להיזהר מאוד כשעושים cast למצביע.
כי הוא למעשה אומר: "המידע בזיכרון הזה מייצג את מה שאני חושב, ולא מה שבאמת אחסנתי שם".
יש טריקים יפים שאפשר לעשות עם זה, למשל לפרק int גדול לבתים נפרדים ע"י הצגתו כמערך (במקום להשתמש ב-bit shift) או להפוך מערך בתים למבנה, אבל יותר קל להסתבך עם זה וליצור באגים.
 

הפרבולה

New member
רציתי להוסיף עוד תכונות לקבוצת מחלקות קיימות שהם כבר בנים

של מחלקות אחרות ( שאני לא יכול לגעת בהם ), למשל קוד קיים
קוד:
class A : public fatherA
{
	....
};
class B : public fatherB
{
	....
};
fatherA *pa;
fatherB *pb;

כעת אני רוצה להוסיף תכונות משותפות ל A ו B על ידי זה שאני מוריש את A ו B מעוד מחלקה שיש לה גם פונקציות וירטואליות, ככה
קוד:
class myNewCommunClass
{
 virtual myFunc();
};
class A : public fatherA , myNewCommunClass
{
};
class B : public fatherB , myNewCommunClass
{
};

כעת השאלה ,איזה טיפוס לתת למצביעים pa pb . כנראה שהכי בטוח זה לתת להם טיפוס של A B , אבל בד"כ אני מגדיר את טיפוס המצביעים להיות מהסוג של האבא ( שהינו משותף לעוד מחלקות אחרות ), הבעיה שכאן יש לי 2 אבות...

מה זה מצביעים חשופים ? מצביעים מסוג void ?
 

BravoMan

Active member
אז ככה:

1. "מצביע חשוף" הוא כל מצביע סטייל C.
כלומר, כל דבר עם כוכבית.
&nbsp
קרא כאן להסבר מפורט על איך עדיף להימנע מהם אם אתה עובד ב-++C ולא ב-C.
https://msdn.microsoft.com/en-us/library/hh279674.aspx
&nbsp
2. אין סיבה שתשנה את טיפוס המצביעים במקרה שאתה מתאר.
אם בחרת לאחסן מצביע לאובייקט במצביע למחלקת אב, סימן שהקוד שישתמש במצביעים האלה אינו מודע בכלל למחלקת בן של האובייקט, ואמור לעבוד רק עם מחלקות שיורשות מאותו אב, ולהפעיל רק את המתודות של האב (או הגרסה שלהם מתוך הבן).
&nbsp
לקוד כזה לא יעזור שהוספת פונקציונליות כלשהי למחלקת בן, כי הוא לא יודע (או לפחות לא אמור לדעת) שיש לו למעשה מחלקת בן.
&nbsp
אלא אם מראש הקוד שלך בנוי דפוק ואתה משתמש במצביעים למחלקות אב בלי סיבה טובה.
 

Grosseto

New member
זה חצי C חצי C++

למרבה הזוועה הרבה מתכנתים כותבים בצורה כזאת
 

selalerer

New member
ה-C style cast הוא טיפש. הוא מביא לך מצביע לתחילת האובייקט.

האובייקט B מורכב מ-A1 ואז A ואז השדות של B. כלומר המבצביע p2 למעשה מצביעה ל-A1. סתם כי הוא ראשון באובייקט מסוג B.
&nbsp
אז אתה קורא ל-get. גם במצביע מסוג A זו קריאה וירטואלית. בקריאה וירטואלית סדר הפעולות הוא:
- לקרוא את המצביע ל-VTAB.
- לקרוא את המצביע לפונקציה.
- להריץ את הפונקציה.
&nbsp
אז מה שקורה פה זה:
- קורא את המצביע ל-VTAB של A1.
- קורא את המצביע לפונקציה הראשונה (והיחידה) ב-VTAB הזה שזה הכתובת של momo של A1.
- מפעיל את הפונקציה, כלומר את momo.
&nbsp
&nbsp
 

הפרבולה

New member
אוקי

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