עבור לתוכן

שאלה קטנה ב C++

Featured Replies

פורסם

שלום לכולם.

אני יוצר מחלקה A ומחלקה B.

אני מעוניין שמשתנה מסויים מ-A יהיה מצביע למחלקה B או לחילופין שפונקציה תקבל/תחזיר מצביע למחלקה B.

אני מגדיר B* PB האם אני צריך לעשות include ל-b.h בקובץ a.h או ב-a.cpp ?

אני מקבל שגיאה שחסר ";" לפני הכוכבית של המצביע, C2143.

מקווה שהייתי ברור.

תודה לעונים.

פורסם

בד"כ עדיף לעשות include.

אבל לא תמיד זה רצוי או אפשרי.

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


class B; // forward decleration

class A
{
// ...
void foo(B* ptr_to_b);
// ...
};

פורסם
  • מחבר

רק שאני עושה include וגם forward declaration אז כל השגיאות נפתרות לי, הגיוני?

דבר נוסף: כשאני מצהיר ב-A על פונקציה מ-B כ- friend אז מופיעה לי שגיאה שאני לא יכול לגשת למשתנים פרטיים של A, אבל שאני מצהיר על כל המחלקה B כ-friend אז זה בסדר.

friend void Room_List::add_room(Room* room);

ניסיתי לשים את ההצהרה גם ב-public וגם ב-private אותה שגיאה בשניהם.

פורסם

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

רק שאני עושה include וגם forward declaration אז כל השגיאות נפתרות לי, הגיוני?

בקובץ ה-h. עושים את ה-forward declaration ובקובץ ה-cpp עושים את ה-include.

בעקרון עדיף להימנע מלשים includeים בקובץ h. תזכור שאם קובץ א' עושה אינקלוד ל-ב', אז כל מי שעושה אינקלוד ל-א' עושה אינקלוד גם ל-ב'. ואם גם ב' עושה אינקלוד לקבצים ג' ו-ד' אז גם כל מי שעושה אינקלוד ל-א' יעשה אינקלוד גם להם... מהר מאוד אפשר להגיע ככה לעץ גדול מאוד של אינקלודים מיותרים שסתם מנפח את זמן הקומפילציה וכנראה גם את גודל התכנית המקומפלת.

פורסם
  • מחבר

אני חייב להשתמש ב-friend פה.

אוקיי נראה לי הבנתי את הבעיה:

נניח שהצהרתי על פונקציה כלשהי כ-friend ב- A וכעת אני משתמש בתוך הפונקציה במשתנה X מתוך המחלקה אליה הפונקציה שייכת (B) והמשתנה X מנסה לשנות משתנה פרטי בתוך מחלקה A, אזי אני צריך להגדיר גם את המשתנה כ-friend נכון?

אלו שני מחלקות שזורות אחת בשנייה, לכן אני חייב להשתמש ב-friend וב-include-ים

דבר מוזר:

יצרתי מחלקה נוספת שמשתמשת במחלקה אחרת ולא עשיתי include ולא forward declar' אבל אני לא מקבל שגיאה/אזהרה.

פורסם

לא צריך להגדיר את המשתנה כ-friend, רק את הפונקציה.

אם אתה רוצה שהפונקציה A::f תכיר את האיברים של B, אז צריך להצהיר עליה כ-friend בתוך B, יענו:

class B {
friend void A::f(B*);
};

אבל אם כמו שאתה אומר שתי המחלקות שזורות זו בזו, כנראה אין בעיה להצהיר על A כחבר של B וזהו.

יצרתי מחלקה נוספת שמשתמשת במחלקה אחרת ולא עשיתי include ולא forward declar' אבל אני לא מקבל שגיאה/אזהרה.

קשה לדעת בלי לראות מה בדיוק עשית. יכול להיות שעשית אינקלוד לאיזה קובץ אחר שעושה אינקלוד לקובץ שמכיל את ההגדרות שאתה צריך.

פורסם

אני מוכרח להגיד שלא התעמקתי בכל ההודעות בעמוד, אבל אני רוצה לחזק את שתי הטענות שהועלו בדבר friend ו include.

האחת, איפה שאפשר, תשתמש בforward declaration ב header files במקום ב include.

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

לגבי גודל הבינארי - אני לא חושב שזה משנה (אל אם עשית include למשהו שלא צריך). ה header רק אומר לקומפיילר (אלא אם יש inline) מה ההגדרה של הפונקציה הוא המחלקה, כדי שהוא ידע להחליף התייחסויות למחלקה או הפונקציה בכתובת שלהם בבינארי הסופי.

השנייה, friend - אני כבר 8 שנים מהנדס תוכנה בהייטק. ראיתי שימושים ב friend במהלך השנים, אבל זה היה כל-כך מעט פעמים, שאני אפילו לא זוכר את הדוגמה. אף אחד מהם לא אני כתבתי. זה מעיד על design גרוע, מוביל coupling מיותר, לבאגים, ולקוד מכוער.

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

לגבי השאלה האחרונה - כמו שאמרו, כנראה שאחד הקבצים שעשית להם include עשה include למחלקה שלך (או שמכיל בעצמו forward declaration) - אין אפשרויות אחרות. אם הקומפלייר לא היה מכיר את המחלקה שלך כשהוא עובר על ה copilation unit המסוים, הוא היה נכשל.

פורסם

השנייה, friend - אני כבר 8 שנים מהנדס תוכנה בהייטק. ראיתי שימושים ב friend במהלך השנים, אבל זה היה כל-כך מעט פעמים, שאני אפילו לא זוכר את הדוגמה. אף אחד מהם לא אני כתבתי. זה מעיד על design גרוע, מוביל coupling מיותר, לבאגים, ולקוד מכוער.

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

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

לקריאה נוספת:

http://www.parashift.com/c++-faq-lite/friends.html

http://drdobbs.com/cpp/184401197 (של סקוט מאיירס)

Design and Evolution of the C++ Programming Language

ועוד (חפשו בגוגל)

עריכה: למקרה שזה לא היה ברור, בגדול gmorphus צודק, ובד"כ friend זה לא הדבר הנכון. כמו כן הרמה בתעשייה נמוכה.

פורסם

תודה על ההתייחסות.

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

בעניין ה friend - באמת שלא נתקלתי במקרים שבהם זה לא נעשה כפתרון "מלוכלך". זה שובר לחלוטין את עקרונות התכנות מונחה העצמים. גם את עקרון ה encasulation וגם את עקרון ה decoupling.

במידה ושתי מחלקות הן עדי כדי כך צמודות, ניתן להכניס אחת בתוך השנייה או להשתמש בירושה/ממשקים וכו'.

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

יש דברים נוראיים יותר מfriend...

פורסם

בעניין ה friend - באמת שלא נתקלתי במקרים שבהם זה לא נעשה כפתרון "מלוכלך". זה שובר לחלוטין את עקרונות התכנות מונחה העצמים. גם את עקרון ה encasulation וגם את עקרון ה decoupling.

במידה ושתי מחלקות הן עדי כדי כך צמודות, ניתן להכניס אחת בתוך השנייה או להשתמש בירושה/ממשקים וכו'.

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

אסתפק בזאת: הטענה לעיל מקורה לפעמים בחוסר הבנה של מה friend עושה בדיוק ומה תפקידו בשפה לעומת שפות אחרות. friend אינו שובר encapsulation, אלא מפחית ממנו באופן מקומי (כמו גם member functions). התוצאה הסופית יכולה להיות חיזוק כולל של encapsulation (ברמה של מודול או פשוט מחלקה חיצונית). זו עוד דרך לבקרת גישה, שניתן לנצל אותה לטובה או לרעה.

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

אגב כמובן שיש מקומות טובים יותר. התכוונתי שהרמה הממוצעת בתעשיה היא נמוכה :(

פורסם
  • מחבר

תודה לכולם על ההתייחסות ועל מידע הנוסף.

זהו תרגיל בית שניתן לי, וניתנו לי מחלקות מסויימות שאני חייב לממש בעזרת החומר שנלמד עד כה בלבד, לכן השימוש ב-friend הכרחי וגם באינקלוד בכמה מקרים.

שוב תודה לכולם, אני בינתיים ממשיך לשבור את הראש על התרגיל...

ארכיון

דיון זה הועבר לארכיון ולא ניתן להוסיף בו תגובות חדשות.

דיונים חדשים