שאלה ב CPP

make clean

New member
שאלה ב CPP

איך הקוד הבא עובד ? כלומר איך הקומפילר יודע ליצור LIST ואיטרטור שלה רק על סמך FORWARD DECLARATION?

#include <list>

using namespace std;

struct A;
struct B;


struct B {
list<A>::iterator itA;
};

struct A{
list<B>::iterator itB;
};
 

BravoMan

Active member
הקומפיילר לא יוצר list.

הוא יקרא את התיאור של התבנית מקובץ list.hpp וידע לבנות קריאות מתאימות לפונקציות של אובייקט מסוג list.

הקוד של הפונקציות כבר מצוי בספרייה וזה יהיה תפקידו של הלינקר לקשר את קובץ האובייקט (לא לבלבל עם אובייקט של ++C) שנוצר מהקוד שצירפת לקוד של הפונקציות הרלוונטיות בספריה.
 

make clean

New member
לא הבנתי

השאלה שלי היא איך ב STRUCT B אפשר להגדיר איטרטור ל LIST של A כאשר A עוד לא מוגדר ואי אפשר להגדיר LIST של A ( ה TEMPLATE אמור להתבצע אז והקומפילר לא יודע איזה גודל כל "NODE" ב LIST יכיל אז איך הוא יקבע גודל של NODE ) ?
 

nocgod

New member
יש מצב זה תוצאה

של תהליך הקומפילציה עצמו
בתהליך הקומפילציה הקוד עובר כמה שלבים:
בשלב הראשון עובר lexer (מנתח לקסיקלי) מייצר tokens אותם הוא שולח לparser, כאשר הוא מגיע לשורה של הlist<a> הוא רואה a ומייצר token של מזהה (identifier).
בשלב שני הparser ( מנתח סינטקטי) מקבל את כל האסימונים ורואה שמבחינת תחביר השפה אין שום בעיה, בשלב הזה אני מניח שהוא יודע איך לגזור את A כי הוא מבין שA הוא לא מזהה אלא טיפוס (כי זה הכלל גזירה שלו)
וכטיפוס הוא פשוט גוזר אותו בהתאם לכללים אותם הוא פגש כבר/מכיר

כנראה זה קצת far fetched אבל זה נראה לי המצב...כאשר אתה עושה forward declaration זה כאילו אתה אומר לקומפיילר "יש כלל גזירה כזה, תחפש אותו בהמשך" החשיבות של זה היא שלא יהיו טעויות בזמן קומפילציה מסוג הגדרה כפולה.
 

make clean

New member
תגובה

אם זה היה ככה אז יכלת להגדיר בתוך B גם שדה מטיפוס A ולא רק LIST/ITERATOR .
 

nocgod

New member
ואתה בטוח שאתה לא יכול לעשות את זה?

אני דווקא חושב שאתה כן יכול לעשות

typedef struct A;
typedef struct B;

struct B
{
A a;
}
 

selalerer

New member
הממ.. אולי מקמפל לך ל-11++C.

אם כן, יכול להיות שהקובץ הדר list מכריז על list כ-extern template ואז ה-instanciation נעשה בשלב מאוחר יותר (כי אין לקומפיילר עדיין את המימוש של list).

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

מה שבטוח הוא שב-++C בסטנדרט הישן יותר, זה לא עובר קומפילציה.
 

BravoMan

Active member
בדקתי את זה עכשיו עם פרמטרים

ansi ו-pedantic ל-++g גרסה 4.5.2 וזה מתקמפל ללא שגיאות או אזהרות.

אבל, עצלי לא מותקן boost כך שהקומפיילר פונה לקובץ list מהספרייה הסטנדרטית של ++C.
השגיאות בקישור שהבאת נובעות מהמימוש של boost וכנראה שם נעוצה הבעיה.

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

nocgod

New member
מאפשרים זאת כללי הדקדוק של השפה

והעובדה שהcode generator כנראה מכיר כבר את כל הטיפוסים שיכולים להיות מוגדרים הודות לעובדה שהsemantic analyzer דאג לעבור כבר על הקוד...כנראה...
 

selalerer

New member
הקומפיילר לא יכול להכיר את A ואת B לפני list.

A ו-B מורכבים ממשהו של list.

כדוגמא אחת לבעיה: הקומפיילר חייב לסיים instantiation של list בכדי לדעת מה ה-sizeof של A ושל B. אם בקוד של list יש new T אז הוא חייב לדעת בנקודה הזו את ה-sizeof של T, אבל כאמור זה תלוי ב-instanciation של list שעדיין לא הסתיים.

כנראה שבתקן החדש החליטו על דרכים לעקוף את הבעיה, אני לא מכיר אותו מספיק טוב.
 

nocgod

New member
אני לא חושב שזה קשור לתקן החדש בכלל

לפי איך שאני מבין את זה רוב הקומפיילרים מזמן כבר לא one-pass compilers, עובדה יש לך forward declaration בC ו ++C
כמו כן אני אזכיר לך שאתה לא מריץ את הקוד שאתה כותב, אלא מריץ תוצר של תהליך קומפילציה.

איך שאני מבין את זה, זה מתאפשר בגלל שמבחינה דקדוקית זה נכון אז אתה מקבל עץ parsing מלא ומבחינה סמנטית זה גם נכון כי בפועל אתה מכריז שיש לך A ומכריז שיש לך B (לא מגדיר)
כאשר מגיע שלך הcode generation כבר ידועות ההגדרות של כל הטיפוסים אותם צריך לתהליך הקומפילציה והוא יכול לגשת לתהליך ייצור הקוד.
בגלל זה אם אתה מסיר לצורך העניין את הforward declaration של A תהיה לך שגיאה כי אתה לא מכריז על A ככה שכשהוא יגיע לB ויראה שהוא בעצם לא יודע מה זה A הוא פשוט לא ידע איך להמשיך, גם אם אתה מגדיר בפועל את A אחרי B
אגב תשים לב שבשפות כמו #C אין חובה של forward declaration לצורך העניין אתה יכול לכתוב את המחלקה בסדר הזה: מתודות (גם להן אין חשיבות לסדר, מה שחשוב בC אם אין forward declaration) ואז רק משתנים שהמתודות משתמשות בהן

שים גם לב שהקומפיילר לא עושה instance לכלום. את זה עושה הקוד תוצר של תהליך הקומפילציה בזמן ריצה.
 

selalerer

New member
אני חושב שיש לנו אי הבנה במושגים.

instantiation בהקשר של templates זה היצירה של המחלקה, לא יצירה של אובייקט.
 

nocgod

New member
לעומת שפות אחרות

++C מייצר את הtemplates שלו בזמן קומפילציה ולא בזמן ריצה, על כן אני לא מבין לאן אתה חותר, מאחר והם נוצרים בפועל רק בשלב הcode generation, כי הם ללא שום ספק עוברים בלי בעיה את השלבים הראשונים של הקומפילציה לפי חוקי השפה
 
אם אני זוכר נכון

פונקציות של מחלקות עם תבנית נוצרות רק כאשר משתמשים בהן.
אולי כל ה-new T בקוד של list נמצא בתוך פונקציות שעוד לא נוצרו?

(אני לא כ"כ שולט ב-C++. אשמח לתיקונים והבהרות)
 

selalerer

New member
אני חושב שהתקן לא מחייב כך ולא אחרת.

אני יודע שבקומפיילר של VS זה ככה ובקומפיילר של אינטל ללינוקס זה לא ככה.
 

mandymo

New member
שאלה יפה

אבל לא ממש נכונה

הקומפיילר לא יוצר LIST כלשהו. בשום מקופ לא כתוב לו לייצר כזה...
הוא כן מייצר איטרטור אחד בכל struct . העניין הוא, שבאיטרטור, יש רק פויינטר לאובייטקט, ולא אוביקט מלא, ולכן ניתן לייצר כזה, רק על סמך forward deceleration.
 

make clean

New member
הוא כן אמור לייצר

כשאתה מגדיר איטרטור ל LIST אתה יוצר INSTANCE שמגדיר מחלקה חדשה - LIST של A . מכיוון ש LIST עובדת BY VALUE חייב להיות היכן שהו CLASS/STRUCT שמכיל A שגודלו עדיין לא ידוע.

אני מסכים שאיטרטור ל LIST מכל טיפוס צריך להיות באותו גודל אבל כדי לייצר אותו צריך לייצר את כל המחלקה לא?
 

mandymo

New member
שני דברים

לא מבין מה המשמעות של "יוצר INSTANCE שמגדיר מחלקה חדשה...
בגדול, אין כאן שום קסם. השאתה יוצר איטרטור, מסוג כל שהוא, נוצר דבר אחד - איטרטור מאותו סוג (לא מאותחל). לא חייב להיות שום container מהסוג של האיטרטור. כדי ליצור container כזה, אתה לציר לייצר אותו.
מעבר לזה, גם אם הייתה יוצר LIST אמיתי(לדוגמה ב struct B,) זה עדיין היה מתקמפל. יש לך תשובה לזה למעלה .
 
למעלה