שאלה עקרונית בC...

neko

New member
שאלה עקרונית בC...

מה אמור להיות בקבצי הH? HEADER-ים של פונקציות כך הבנתי, אבל האם גם הDEFINE-ים? והאם גם הINCLUDE-ים? האם יש יוצאים מן הכלל? תודה מראש, כרגיל, לטורחים...
 

עופר ב.ה

New member
תשובה

יש לצרף את ה- includes הנחוצים ל prototypes שבקובץ ה- h, ורק אותם. את ה includes הנחוצים למימוש הפונקציות בקובץ ה- C יש לעשות בקובץ ה- C. בקשר ל- defines - הרבה זמן לא כתבתי C, אני לא זוכר.
 

צבתות

New member
כמו שאמרו...

העיקרון המנחה הוא, אם אתה משתמש במשהו בקובץ ה-H, אתה צריך להציג את המקור שלו. כלומר, ההצהרות על הפונקציות שאתה רוצה לחלוק אתה שם ב-H (פונקציות לשימוש עצמי אתה מסתיר בקובץ המימוש). אם אתה משתמש ב-define בקובץ ה-H בצורה כלשהי, אתה גם צריך להציג הצהרה שלו שם. למשל, אם אתה שולח לפונקציה כפרמטר מצביע למערך בגודל MAX_SIZE, המשתמש חייב לדעת מהוא הגודל הזה כדי לא לחרוג ממנו, ובגלל זה הוא חייב לראות את ה-define שעשית שמגדיר את MAX_SIZE. כנ"ל ל-includes. אם אתה משתמש בטיפוס X, שהוגדר בקובץ X.h, ויש לך איזו פונקציה ב-header שמקבלת משתנה מטיפוס X למשל, המשתמש חייב לדעת מהו הטיפוס הזה, ולכן אתה חייב לציין את המקור, כלומר לשים את ה-include בקובץ ה-H. בהצלחה!
 

אלדד28

New member
הסתייגות לקודמיי

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

צבתות

New member
מממ...

נניח שאתה משתמש בפונקציות העושות שימוש בטיפוסים שלצורך הגדרתם אתה צריך לעשות include. את ההצהרות אתה רושם בקובץ ה-H. עכשיו, בין אם נעשה include כבר לקובץ שמכיל את הטיפוסים או לא, אתה חייב לדעתי, ולו רק עבור שלמות העבודה, לכתוב include על הטיפוסים שאתה משתמש בהם ב-H. למשל, נניח שאתה משתמש ב-class A המוגדר ב-a_header.h. יש לך גם class B, שמכיל פונקציה שמקבלת כפרמטר טיפוס מסוג class A.
class B { ... public: void func (A a); }​
מי שיקרא את קובץ ה-header של class B (להלן b_header.h), יחפש הגדרה כלשהי על class A, אבל לא ימצא. אם ה-include מופיע במימוש ו/או בקובץ שעושה שימוש בשני ה-headers, אבל לא לוקאלית בקובץ שמשתמש בו, לדעתי זה חור פעור, שמן הראוי לסתום אותו. ואני לא רואה שום סיבה להמנע מכך.
 

אלדד28

New member
אה, זה פשוט מאוד.

1. אתה לא עושה A a אלא A &a; ומכיוון שכך - 2. אתה מכריז ;class A לפני שאתה כותב את B. לא צריך שום include נוסף. בנוסף, ראה את תגובתי לאייל.
 

eyalbd

New member
הסתייגות לקודמי ../images/Emo13.gif

אמר פעם Scott Meyers באחד הספרים שלו
When in doubt - do as a simple int does (ScottM)​
הוא כתב בהקשר של אופרטורים למחלקה. אפשר להרחיב את הקביעה להקשר של קבצי כותרת ולומר:
When in doubt - do as the standard library does (EyalB...)​
בוא נראה: - האם אי פעם קיבלת הודעת שגיאה בקובץ כותרת של הספריה הסטנדרטית משום שלא הוכלל קובץ אחר? - האם בעת הכללת קבצי כותרת של הספריה הסטנדרטית היית חייב לדאוג לסדר מסויים? (האמור לעיל מתייחס לספריה הסנדרטית של C וגם ++C אבל לא רק - ספריות איכותיות אחרות גם מתוכננות כך) התשובה שלילית בשתי השאלות - דהיינו קובץ כותרת שמספק ממשק דואג בעצמו לכל התלויות שלו. אם אתה כותב סט של קבצי כותרת או ספרייה כלשהי - דאג לזאת בעצמך למשל:
#ifndef SOME_FILE_H #include "some_file.h" #endif​
עליך כמובן לדאוג לinclude guard בקבצי ה-H שלך ע"מ שזה יעבוד. אגב אם דאגת ל-include guard אין חובה לכתוב מה שכתבתי למעלה. הדבר האחרון שאני רוצה הוא לקבל ממשק שכופה עלי לדאוג לקבצי הכותרת או לסדר שלהם במקרה הטוב או "השלמת החסר" באופן כזה במקרה הרע:
typedef unsigned long DWORD; typedef int BOOL; // etc...​
וגם את זה ראיתי.
 

אלדד28

New member
ממשיך להסתייג

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

eyalbd

New member
עניין של גישה

אין כתיבה של ספרייה דומה לכתיבת תוכנית! אם אתה כותב לעצמך include-ים כחלק מהתוכנית שלך - ניחא. אבל אם אתה כותב ספרייה לשימוש כללי - כאן אין פשרות. אתה חייב שהממשק שלך יהיה שלם אחרת יהיו קיצורי דרך ע"י forward declarations או כל מיני נפילות דומות. כבר ניתקלתי בממשק שהשתמש ב-HWND אבל הטיל עלי את האחריות להכליל את windows.h. זו דוגמא טריויאלית אבל ראיתי דוגמאות פחות טריויאליות. מי שסיבך את עצמו בלופ של includ-ים נפל בגללו עצמו (או בעברית תקנית זבש"ו) כי לא טרח קודם ללמוד קצת יותר.
 

אלדד28

New member
נמשיך...

1. מי שהסתבך בלופ זה בגלל שהוא לא תכנן מראש את כל עשרות ה-include-ים שלו כמו שצריך. אני לא מתכוון ללופ פשוט של A קורא ל-B ו-B קורא ל-A. אני מתכוון ללופים כאלו שאין לך סיכוי לצאת מהם. כאלו שבהם A קורא ל-B קורא ל-C קורא ל-D קורא ל-E קורא ל-F, שקורא ל-M שקורא ל-C. 2. נכון, הממשק חייב להיות שלם כשאני כותב ספריה. לכן, כשאני כותב ספריה אני מספק קובץ include אחד ויחיד שמכליל כל מה שצריך בשביל להשתמש בספריה. בדיוק כמו ש-windows.h מכליל את כל מה שצריך בשביל WINDOWS API, אני מייצר MY_MODULE.H שמכליל את כל מה שצריך בשביל המודול הזה.
 

אלדד28

New member
ובהמשך לנקודה הראשונה שלי..

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

selalerer

New member
משהו שאני לא מבין.

אם לכל קובץ H יש את הifndef שלו בתחילת הקובץ (גם לפני inludים) אז איך יכול להיווצר לולאה?
 

אלדד28

New member
בוא נניח משהו כזה:

FIRST.H - #ifndef _first_h #define _first_h #include "second.h" #define ABC 10 #define DEF ABC+GHI #endif SECOND.H - #ifndef _first_h #define _first_h #include "first.h" #define GHI ABC+DEF #endif​
 

עופר ב.ה

New member
הדוגמה שתיארת היא פשוט שגיאה

של copy & paste. אם יש קונוונציה לפיה שם ה define הוא שרשור של שם המודול ושם הקובץ, אין סיבה למעגליות כזאת. בנוסף - אני מוצא עוד שני חסרונות בגישה הזו: 1. אם אתה מוסיף למודול של קובץ נוסף, שדורש שירות מקובץ h שעוד לא משתמשים בו במודול, תצטרך לקמפל מחדש את שאר המודול. 2. בכלל, למה שקבצים יכירו מחלקות (או שירותים, או מודולים אחרים) שהם בכלל לא משתמשים בהן? הגישה שלך מתאימה, לדעתי, למקרים שבהם יש h-ים שמשתמשים בהם כמעט בכל קובץ במודול, ואז שווה לקבץ אותם לקובץ h יחיד, אבל כשקובץ מקור א' הוא המשתמש ביחיד ב some_service.h, וקבצי מקור ב' - ת' לא משתמשים בו, אני חושב שזה רעיון גרוע להכניסו ל my_module.h.
 

אלדד28

New member
ב-my_module.h

נמצאים רק הקבצים שצריכים להיות "public". יש בנוסף עוד h-ים שמשמשים קבצים פנימיים בתוך המודול, כמובן. הם לא יופיעו כ-include-ים בקבצי h-ים אחרים, אלא יוכללו מה-CPP שצריך אותם ספציפית. זה עונה על שתי הנקודות שלך. בכל מקרה, אני עובדתית עובד ככה על פרויקט ענק, וזה מתקתק מצוין והרבה יותר נוח.
 
למעלה