מה חשיבות ה virtual destructor בc++

Pembelton

New member
נניח ש- Y יורש מ - X

ו-p הוא פוינטר לאוביקט מסוג X. כאשר אתה מבצע delete p, אם p מצביע על אוביקט Y אז אתה רוצה שייקרא הדיסטרקטור של Y ולא רק הדיסטרקטור של X. ע"י הגדרת הדיסטרקטור כ VIRTUAL אתה מבטיח שכך אכן יקרה
 

eyal the one

New member
כן ברור אבל..

מה הם הסיבות שאני רוצה שיקרא זה של הבן? הבנתי שזה קשור בזה שאם האב מצביע על מקום בזיכרון אז אם הוא מצביע ל new של בן, והבן יותר גדול ממנו, אז אם הdtor לא יהיה וירטואלי לא ישתחרר כל המקום של הבן, ובמקרה כזה לא צריך לשים שום תוכן בפונקציית ה dtor , אלא רק שתהיה שם על מנת שכל זכרון הבן ישתחרר, האם זה נכון?
 

Pembelton

New member
זה לא מדויק

ההסבר הוא הרבה יותר פשוט: אם יצרת אוביקט מסוג Y, אז אתה רוצה שכאשר האוביקט הזה ישוחרר, הדיסטרקטור שיופעל יהיה הדיסטרקטור של Y (ולא דיסטרקטור של מחלקה אחרת)
 

eyal the one

New member
טוב, אני אשאל את זה אחרת

נניח ויש לי אב שיורש ממנו בן ובבן יש int נוסף שלא נמצא באב. גם במחלקת האב וגם במחלקת הבן אין שום הקצאות דינמיות. יש לי מצביע של האב שמצביע על new של הבן. אם לא השתמשתי ב dtor וירטואלי, האם כל הזיכרון של הבן ישוחרר כשאני יעשה delete למצביע של האב?
 

annefan

New member
מה כן?

זו התנהגות לא מוגדרת - Undefined Behaviour. יכול להיות שהכל יהיה בסדר, יכול להיות שתווצר נזילת זכרון ויכול להיות שהמחשב ישמיד את עצמו בהבזק אור מסמא. השאלה היא למה אתה לא רוצה DTOR וירטואלי?
 

Pembelton

New member
תרשה לי לתקן:

אני מסכים איתך שזה גועל נפש לעשות delete על מצביע ל BASE CLASS כאשר הדיסטרקטור הוא לא וירטואלי. ולכל מי ששואל הייתי ממליץ בחום להגדיר את הדיסטרקטור כוירטואלי כדי להמנע מהסכנות שהוא עלול לגרום. מצד שני, אני לא מסכים איתך שזאת התנהגות לא מוגדרת. להפך: ההתנהגות מאד מוגדרת: אנחנו יודעים מי יופעל (הדיסטרקטור של ה BASE CLASS), ואנחנו יודעים בדיוק מה הוא יעשה.
 

annefan

New member
Undefined Behaviour

מה יקרה לדעתך לזכרון הנוסף של הבן? אמרת נכון, שמופעל ה-DTOR של האבא. ה-DTOR הזה לא משחרר את הזכרון שנוסף בבן. מצד שני, אוביקט הבן יצא מה-scope, לא נגיש יותר, whatever. לכן, אנחנו לא יודעים מה קורה לאותו זכרון -> אנחנו לא יודעים מה קורה לאוביקט -> התנהגות בלתי מוגדרת. (לכן לא ברור לי למה כתבת שאין חשש לנזילת זכרון)
 

Pembelton

New member
כאשר אתה מבצע delete

כל הזכרון שהוקצה משתחרר. הבעיה היא רק עם הדיסטרקטורים
 

annefan

New member
ובכל זאת, Undefined Behaviour

אתה רוצה את הציטוט מהסטנדרט? אתה רוצה את הציטוט מהספר של סטרוסטרופ? אתה רוצה ציטוט מ-FAQ? כולם אומרים שזה Undefined Behaviour. אתה אומר שלא. משום מה אני מעדף להיות בטוח ולסמוך עליהם. שום דבר אישי, אבל כמעט תמיד יש צורך ב-DTOR וירטואלי, כאשר יורשים מהמחלקה שלך. לא תמיד, כמעט תמיד. כמו שאוהבים להגיד בקבוצות דיון על ++C, בהתנהגות לא מוגדרת, השפה יכולה לפרמט את הדיסק הקשיח שלך. למה? כי זו התנהגות לא מוגדרת. סביר שזה לא יקרה, אבל גם מאוד סביר שיהיו לך באגים מוזרים, לא תמיד עקביים, כל מיני מריחות זכרון וצרות אחרות. למה? כי זו התנהגות לא מוגדרת. אם אתה יודע מה אתה עושה (אין לך פונקציות וירטואליות אחרות במחלקה, המחלקה שלך איננה מיועדת לירושה (STL כדוגמא), אתה רוצה לחסוך ביצועים), אתה יכול לכתוב DTOR לא וירטואלי. אם אתה לא יודע מה אתה עושה, ויירשו מהמחלקה שלך, כמעט תמיד אתה צריך DTOR וירטואלי. ואם לא? Welcome to the wondefull world of Undefined Behaviour. כל טוב ממני (שיצא לי כבר לרדוף אחרי באג שנבע מהתנהגות כזו במשך ארבעה ימים).
 

Pembelton

New member
בדיוק הלכתי לסטנדרט בעצמי ומה מצאתי

In the first alternative (delete object), if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand's dynamic type and the static type shall have a virtual destructor or the behavior is undefined אתה צודק לגמרי.
 

annefan

New member
אני לא מחפש להיות צודק ../images/Emo13.gif

לפני שבועיים רדפתי אחרי באג של מתכנת (אחר, לא אני, אצלי אין באגים
) שסמך על התנהגות לא מוגדרת (אחרת), וכיוון שההתנהגות היתה לא מוגדרת (DUH!) התופעות השתנו כל פעם. אחרי ארבעה ימים מצאתי את הדרעק. עניינית, גם בלי הפורמליסטיקה של הסטנדרט, מספיק שיש בבן: א. מצביע ב. ב-CTOR יש הקצאת זכרון למצביע ג. ב-DTOR יש שחרור. אם ה-DTOR לא וירטואלי אז: 1. הבן נוצר 2. זכרון מוקצה 3. השמת מצביע לבן לתוך מצביע לאבא 4. delete למצביע 5. קריאה ל-DTOR (של האבא!!) 6. הזכרון מ-2 לא משתחרר. =================== Memory Leak (שהוא אחד מהסוגים של UB)
 

Pembelton

New member
בכל אופן בלי קשר ל- UB

אין ספק שרצוי להגדיר דיסטרקטור כוירטואלי משום שעצם זה שהדיסטרקטור הלא נכון הוא זה שמופעל זה יוצר מספיק בלגן בהתנהגות. ברוב המקרים המפתח לא יודע מראש איך הוא ישתמש במחלקות שלו בחלוף הזמן, ולכן גם לא כדאי להסתמך על השערות מהסוג הבא: "כרגע אף אחד לא יורש, אז אני אגדיר את הדיסטרקטור כ- NON VIRTUAL. אח"כ אם צריך אז אני אשנה את זה"
 

eyal the one

New member
שניה , אני רוצה להבין משהו:

כשאני עושה:
int *p = new int;​
ןלא עושה לאחר מכן בתוכנית delete, אז אין פה שום דבר לר מוגדר, יש לי זיכרון שמור על ה heap , ואם יצאתי מה scope של reference אז נדפקתי עם זיכרון שמור על ה heap שלא ניתן לנצל אותו יותר, שום דבר לא מוגדר. אותו דבר בדיוק בהורשה בדוגמא שנתתי קודם, לא? זיכרון תפוס ושמור של int שאין לי אליו יותר reference אבל מצד שני הוא לא יכול להדרס יותר ע"י התוכנה, וגם פה , שום דבר לא מוגדר. אני טועה?
 

eyal the one

New member
אוף יצא לי ניסוח לא טוב:

נא להחליף את המשפט: "שום דבר לא מוגדר" במשפט "אין פה שום דבר ש 'לא מוגדר' כלומר הכל מוגדר"
 

annefan

New member
זה נושא אחר לגמרי

הקטע עם ה-DTOR סגור? אם כן, יופי. אם לא, תשאל. התנהגות לא מוגדרת היא התנהגות ש: א. נובעת מתכנות לא נכון (כמו הדוגמא שלך), או מנתונים לא נכונים. ב. הסטנדרט אומר עליה שהיא לא מוגדרת (כמו העדר DTOR וירטואלי), או שאיננו מגדיר לה התנהגות. אני לא יודע אם מוגדר באיזה מקום מה צריך לקרות עם זכרון שלא שוחרר. אם אכן זה המצב, אין לך שום מידע על מצב האוביייקט, מה כתוב בזכרון שלו, וכו'. זו התנהגות לא מוגדרת.
 

eyal the one

New member
אז אני שואל:

int func get() { int *p = new int; return 0L; }​
האם זה גם מצב של התנהגות לא מוגדרת?
 

annefan

New member
עברנו לקטע המשפטי?

אני לא יודע האם נזילת זכרון מוגדרת כ-UB, או שבמשתמע היא UB, או שהיא מוגדרת כקוד תקין דקדוקית (ודפוק תכנותית). לא דכור לי שמוגדר במקום כלשהו מה מותר או אסור לתוכנית לעשות עם זכרון כזה. (ב-JAVA אני מניח שה-GC רשאי לפנות אותו).
 

ברנדל

New member
מה ההתלבטות?

זו התנהגות מוגדרת מאוד! זה אומר שיש לך זיכרון שלתוכנית מוגדרת מאוד, הוא שמור לחלוטין, לא ניתן לדרוס אותו, אם תגדיר משתנה חדש על ה heap זה יהיה במקום אחר ולא בבמקום ה int הזה, אם תקרא לפונקציה נניח בלולאה אין סופית אז בסוף יגמר לך הזכרון על ה heap שמוקצה לתוכנית. זה דפוק תכנותית כי אין לך אפשרות להשתמש בו וסתם קיצרת את טווח הזכרון שמוקצה לתוכנית מלכתחילה.
 
למעלה