1.5 - בעלות וניקוי באמצעות RAII - פתרון
פתרון - שדות שנבנו אינם ממתינים לבעלים חיצוני¶
המחלקה החיצונית הבאה מצטרפת למחלקת File מן ההרצאה:
struct OutputPair {
File first;
File second;
OutputPair(const char* second_path)
: first("first.txt"), second(second_path) {
std::puts("pair constructed");
}
~OutputPair() {
std::puts("pair destroyed");
}
};
הקוד הבא מחליף את main:
int main() {
try {
OutputPair pair{"directory-that-does-not-exist/second.txt"};
} catch (const std::exception& error) {
std::fprintf(stderr, "caught: %s\n", error.what());
}
}
כאשר התיקייה חסרה, השדה הראשון נבנה והשני זורק. ההודעה pair constructed אינה מודפסת, וגם pair destroyed אינה מודפסת. בכל זאת ה־destructor של השדה הראשון רץ וסוגר את הקובץ שכבר נפתח.
כאשר שני הנתיבים תקינים, גוף הבנאי מסתיים. בסוף חיי האובייקט רץ קודם גוף ה־destructor החיצוני, ואחריו מושמדים השדות בסדר הפוך להצהרה. אפשר לתת לכל File תווית נוספת כדי להבחין בין שתי הסגירות, בלי להסיק סדר מתוך כתובות.
סגירה מפורשת¶
אפשר להוסיף למחלקת File את הפעולה הבאה:
int close() noexcept {
if (!file_) return 0;
std::FILE* current = file_;
file_ = nullptr;
return std::fclose(current);
}
הפעולה מאפסת את מצב הבעלות לפני החזרת התוצאה. גם אם fclose מדווח על כשל, אין לנסות להשתמש שוב ב־FILE* כאילו נשאר stream תקין. ה־destructor יראה שהמחלקה כבר אינה מחזיקה משאב ולא יבצע סגירה נוספת.
צריך גם לשנות את ה־destructor כך שישתמש בפעולה הזאת. הקורא שרוצה לוודא סגירה בודק במפורש אם close() מחזירה אפס. הדוגמה אינה מבטיחה התמדה על חומרת אחסון; היא רק מפרידה בין דיווח על flush/close של stream לבין ניקוי אוטומטי שלא יכול להחזיר תוצאה.
למה get אינו transfer¶
החזרת pointer דרך get מאפשרת להפעיל פעולות על המשאב בזמן שהוא חי. היא אינה מעבירה את אחריות הסגירה. קריאה ידנית ל־fclose בלי לעדכן את הבעלים משאירה בתוך האובייקט כתובת שכבר אינה מייצגת stream חי.
העתקה אמיתית הייתה דורשת הגדרה של המשמעות: האם שני האובייקטים חולקים מיקום קריאה, האם פותחים קובץ נפרד, ואילו הרשאות או מצב נשמרים. כל עוד לא בחרנו חוזה כזה, דחיית copy היא החלטה נכונה יותר מהעתקת כתובת.