Casting ב- ++C - בעד ונגד.

  • פותח הנושא galh
  • פורסם בתאריך

galh

New member
Casting ב- ++C - בעד ונגד.

רעיון לדיון... מצד אחד, casting יכול לעשות את החיים הרבה יותר קלים כאשר צריכים להחזיק אובייקטים שיש להם הרבה במשותף, אבל יש מקרים שבהם לאחת מחלקות היורשות דרושה פונקציה נוספת שלא שייכת לאב המשותף. אומנם ניתן להגדיר פונקציה וירטואלית שלא עושה כלום, אבל זה פיתרון פחות "יפה". מצד שני, אפשר ב- ++C להשתמש ב- template-ים ואז אין את כל הבעיות של הירושה, אבל זה דורש מתכנת שמכיר את ++C כמו שצריך (בלי להכנס להכללות, אין הרבה כאלו.
). מה דעתכם?
 

vinney

Well-known member
נגד נחרץ

CASTING זה פגיעה בקריאות ובהירות, ונכון שבCPP זה נראה מאוד טריויאלי וברור מאליו שעושים CASTING, ואפילו הומצאו מספר אופרטורים לצורך העניין, אבל עדיין, שימוש בCASTING פוגע בעקרונות הנדסת התוכנה. ניתוח נכון ובניה מושכלת של עץ הורשה תוך שימוש בפולימורפיזם ימנעו את הצורך בCASTING, אפילו בCPP.
 

galh

New member
אז איך תפתור את הבעיה הזו?

יש לך מחלקה שמייצגת עובדים (האב) וממנה יורשים שני סוגי עובדים, מתכנת ומנהל. כמעט כל הפעולות שניתן לבצע על עובד משותפות חוץ מאחת, מנהל יכול לקבל דיווידנד. בהנחה שיש לך רשימה אחת שמחזיקה את העובדים עם מצביעים מסוג "עובד" איך תקרא לפונקציה שאמורה לקבל מצביע מסוג "מנהל" בלי casting? אם לא הייתי מספיק ברור, ניקח דוגמא מ- MFC. חלון דיאלוג (CDialog) יכול לבקש מצביע לאחד הפקדים שבטופס (GetDlgItem). הפונקציה GetDlgItem מחזירה מצביע ל- CWnd. אני כמתכנת יודע בוודאות שהאובייקט שאני מקבל אמור להיות (לדוגמא) מסוג CEdit. איך ניתן לבצע השמה בלי cast?
 

vinney

Well-known member
הבעיה היא בניתוח

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

galh

New member
אבל אמרתי שאין מכנה משותף מושלם!

אז מתוך "עובד" אי אפשר לקרוא לפונקציה ששייכת רק ל"מנהל".
 

vinney

Well-known member
אז תעשה casting

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

voguemaster

New member
נסחפת קצת...

והגישה שהצעת מתחילה להזכיר לי את UnsupportedOperationException שיש בג'אווה במחלקות ה-Container שלהם...
הפיתרון של להוסיף עוד פונקציה ריקה שמחזירה שגיאה הוא מגעיל בטירוף ולא סקלבילי בגרוש. תאר לך שפתאום יש לך מגוון שלם של תכונות ? או מחלקות של תכונות ? זה הופך להיות יותר מדי לאחזקה. לעולם לא תוכל להימנע מ-CASTING ב++C. זה אולי קל להגיד ע"ג הפורום, לדבר זה תמיד קל. זה גם משהו שנורא קל להגיד בספרים, כי בספרים הדוגמאות נוטות להיות יחסית פשוטות שמוכיחות נקודה מסוימת. המציאות דורשת דברים אחרים.
 

Pembelton

New member
כן ולא

כן מסכים איתך שהפתרון של הוספת פונקציה ריקה הוא מגעיל לא מסכים איתך שאי אפשר להמנע מ CASTING. אם תשתמש ב VISITOR תוכל להמנע מכך. (דוגמאת קוד מופיעה בהודעה קודמת שלי בשרשור הזה)
 

vinney

Well-known member
המם..

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

Pembelton

New member
הבעיה היא שלא תמיד אתה יכול לייצר

פונקציה ריקה כזאת. למשל בפונקציה שמחזירה ערך, קשה לדעת איזה ערך צריכה להחזיר פונקציה ריקה שכזאת. המתתכנת בד"כ בוחר להחזיר אפס, אבל זה לא תמיד עובד, כמו בדוגמא הבאה:
struct Employee { int s_; Employee() : s_(50) { } virtual ~Employee() { } virtual int slary() { return s_; } virtual int bonus() { return 0; } }; struct Boss : Employee { Boss() : s_(100), b_(20) { } int bonus() { return b_; } }; float average_bonus(std::vector<Employee*> vec) { int average = 0; for(int i = 0; i != vec.size(); ++i) average += vec->bonus(); average = average / vec.size(); return average; }

זאת כאמור רק דוגמא. יכולות להיות בעיות גם עם פונקציות שלא מחזירות ערכים, וזאת מפני שלא תמיד ברור איך צריכה להראות פונקציה "נייטראלית".
 

vinney

Well-known member
אוף... חזרנו לפילוסופיה

אם יש אפשרות לcasting, ישתמשו בה. עובדה שבעדה הצליחו לעשות מערכות לא קטנות ולא פשוטות, בלי הcasting. תקן אותי אם אני טועה, גם ב#C אין casting, וזאת שפה חדשה שבאה כבר הרבה אחרי ++C. אז נכון שלפעמים זה קשה ודורש לחשוב טיפה יותר בשלב הניתוח, ולהפעיל את הראש כדי להבין איך לבנות מחלקות כך שלא תצטרך להשתמש בcasting, אבל בסופו של דבר זה מביא למוצר טוב יותר, נקי יותר, נכון יותר, ומותאם יותר לפיתוחים עתידיים. לגבי הדוגמא שלך, לא ממש הבנתי מה ניסית להראות בה. תוכל להסביר?
 

Pembelton

New member
כנראה שאל הבנתי אותי

א. אני טוען שצריך להנמע משימוש ב CASTING ב. הדרך הכי טובה, לדעתי, להמנע משימוש ב CASTING היא ע"י שימוש ב VISITOR. פרסמתי דוגמא ל- VISITOR באחת התגובות הקודמות בשרשור הזה ג. ב C# יש CASTING. ד. הדוגמא שלי מראה שיש פונקציות שאתה לא יכול לתת להן מימוש ריק. המחלקה EMPLOYEE מנסה להגדיר פונקציה ריקה שנקראת BONUS ובוחרת להחזיר שמה 0. אבל, זה דופק את הפונקציה של חישוב הממוצע: אתה מצפה שממוצע הבונוסים יחושב רק עבור מנהלים (כי רק הם מקבלים בונוסים) אבל בפועל אתה מקבל בחישוב הממוצע בהרבה ערכי 0 (שמתקבלים מאוביקטים מסוג EMPLOYEE) וכך התוצאה אינה נכונה.
 

vinney

Well-known member
שוב, תוצה של ניתוח נכון

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

Pembelton

New member
הדיון פה לא דיבר על CASTING כללי

אלא על DOWN CASTING (שממומש ב C++ ע"י DYNAMIC_CAST). המצב שתואר הוא מצב שבו אתה מייצג נתון ע"י טיפוס שכן שמתאים לו, אבל בגלל פולימורפיזם יש יותר מטיפוס אחד שמתאים . בשביל להגיע לטיפוס הנכון אתה יכול להשתמש או ב DYNAMIC_CAST (שמבוסס על בדיקה בזמן ריצה) או ב VISITOR שמספק פתרון השנבדק כבר בזמן קומפילציה.
 

vinney

Well-known member
אז תקרא שוב את הודעת הפתיחה ../images/Emo13.gif

הוא שאל על הcasting בכללי דווקא, בלי קשר לפולימורפיזם (אם כי הזכיר פונקציות וירטואלית כאופציה לפתרון). הטענה שלי נשארת נכונה אבל גם למה שאתה מתאר, ואפילו מחוזקת. בכל שנות העבודה שלי לא השתמשתי ב dynamic_cast ושאר האופרטורים של ++C אפילו פעם אחת, בcasting הישן והטוב של C, אני מודה, השתמשתי.
 

galh

New member
הוא דווקא צדק. ../images/Emo13.gif

אומנם השאלה הייתה לגבי casting, אבל הדוגמא היא מקרה שבו הפתרון ה"אקדמי" לא מושלם וצריך לבחור בין פתרון עם casting או פתרון אחר.
 

vinney

Well-known member
פתרון עם casting הוא הפשוט בד"כ

כי כשאתה מגיע למצב שאתה נדרש לבעיה, שלב הניתוח כבר הרחק מאחור, ואתה יכול או לחזור לניתוח לבנות את המערכת מחדש בצורה יותר מסודרת, או "לכבות שריפה" עם casting ולהסתכן בפיתוח מורכב יותר של תוספות עתידיות. בד"כ "כיבוי שריפות" יותר חשוב מתוספות עתידיות היפוטטיות, ולכן נוקטים בפתרון הcasting ועושים שמיכה למנתחי המערכות אחרי זה, אם יש כוח
 

Pembelton

New member
או להשתמש ב VISITOR

ואז לא צריך לעשות שמיכה ולא צריך לכבות שריפה. מה שכן, לכתוב VISITOR זה קצת יותר מסובך משאתר לכתוב DYNMAIC_CAST.
 
למעלה