מעמיקים בנושא הורסים
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 24 מתוך 104.
Destructor היא פונקציית איבר מיוחדת שרצה באופן אוטומטי כאשר אובייקט מושמד. תפקידה העיקרי הוא לשחרר משאבים שהאובייקט רכש במהלך חייו, כגון זיכרון שהוקצה באופן דינמי, ידיות לקבצים או חיבורי רשת.
למחסל יש שם זהה לשם המחלקה, עם סימן טילדה ~ לפניו. הוא לא מקבל פרמטרים ואין לו טיפוס החזרה:
class FileHandler {
std::string filename;
int* buffer;
public:
FileHandler(std::string name, size_t size)
: filename(name), buffer(new int[size]) {
std::cout << "Opening " << filename << "\n";
}
~FileHandler() {
delete[] buffer;
std::cout << "Closing " << filename << "\n";
}
};הורסים נקראים באופן אוטומטי במצבים הבאים:
- אובייקטים במחסנית יוצאים מתחום החיים שלהם
- מפעילים את
deleteעל אובייקט שהוקצה בערימה - משך החיים של אובייקט זמני מסתיים
void example() {
FileHandler f1("data.txt", 100); // הבנאי נקרא
FileHandler* f2 = new FileHandler("log.txt", 50);
delete f2; // המפרק נקרא עבור f2
} // המפרק נקרא עבור f1 (יוצא מטווח ההכרה)בניגוד לבנאים, למחלקה יכול להיות רק הורס אחד. אי אפשר להעמיס עליו.
אם לא תגדיר אחד, המהדר ייצור מפרק ברירת מחדל שלא עושה שום דבר מיוחד — הוא פשוט משמיד כל איבר. עבור מחלקות שמנהלות משאבים, עליך לכתוב מפרק משלך כדי למנוע דליפות זיכרון.
אתגר
קלבואו נבנה מנהל הפעלות שעוקב אחר הפעלות משתמש פעילות ומדגים כיצד הורסים מנקים משאבים באופן אוטומטי כאשר אובייקטים מושמדים, בין אם הם יוצאים מהתחום ובין אם הם נמחקים במפורש.
תיצרו שני קבצים כדי לארגן את הקוד:
Session.h: הגדירו מחלקה בשםSessionשמייצגת הפעלת משתמש פעילה עם נתונים שהוקצו באופן דינמי. למחלקה צריכים להיות:- שדות פרטיים:
username(מחרוזת),sessionId(מספר שלם), ומצביע למערך של מספרים שלמים בשםactivityLogשעוקב אחר פעולות המשתמש, וכןlogSizeעבור גודל המערך - בנאי שמקבל שם משתמש, מזהה הפעלה וגודל יומן. עליו להקצות את מערך יומן הפעילות בערימה, לאתחל את כל האיברים ל-0 ולהדפיס
"Session <sessionId> started for <username>" - הורס שמשחרר את הזיכרון שהוקצה ומדפיס
"Session <sessionId> ended for <username>" - מתודה
logActivity(int activityCode)ששומרת את קוד הפעילות במקום הפנוי הבא (עקבו אחר המיקום הנוכחי באופן פנימי); אם היומן כבר מלא, הקריאה מתעלמת, כך שהמספר לעולם לא יעלה על גודל היומן - מתודה
getActivityCount()שמחזירה כמה פעילויות נרשמו - מתודה
getUsername()שמחזירה את שם המשתמש
- שדות פרטיים:
main.cpp: הדגימו את התנהגות ההורס בתרחישים שונים. קראו מהקלט שם משתמש וגודל יומן (כל אחד בשורה נפרדת), ואז:- צרו בלוק תחום באמצעות סוגריים מסולסלים
{ }, ובתוכו צרו אובייקטSessionשהוקצה במחסנית, עם שם המשתמש מהקלט, מזהה הפעלה101וגודל היומן מהקלט. רשמו שתי פעילויות עם הקודים1ו-2, ואז הדפיסו"Stack session activities: <count>". כשהבלוק מסתיים, ההורס אמור לפעול באופן אוטומטי. - אחרי הבלוק, הדפיסו
"Stack session destroyed" - צרו אובייקט
Sessionשהוקצה בערימה באמצעותnew, עם שם המשתמש"Guest", מזהה הפעלה202וגודל יומן3. רשמו פעילות אחת עם הקוד5, הדפיסו"Heap session activities: <count>", ואז בצעו לוdeleteבמפורש. - אחרי המחיקה, הדפיסו
"Heap session destroyed"
- צרו בלוק תחום באמצעות סוגריים מסולסלים
האתגר הזה מציג את שתי הדרכים העיקריות שבהן הורסים מופעלים: באופן אוטומטי כשאובייקטים במחסנית יוצאים מהתחום, ובאופן ידני כשמוחקים אובייקטים מהערימה. בשני המקרים יש לשחרר כראוי את יומן הפעילות שהוקצה באופן דינמי, כדי למנוע דליפות זיכרון.
כללו את קובץ הכותרת ב-main.cpp באמצעות #include "Session.h".
נסו בעצמכם
#include <iostream>
#include <string>
#include "Session.h"
using namespace std;
int main() {
// קראו את הקלט
string username;
int logSize;
getline(cin, username);
cin >> logSize;
// TODO: צרו בלוק תחום עם סוגריים מסולסלים { }
// בתוך הבלוק:
// - צרו Session המוקצה במחסנית עם שם המשתמש מהקלט, מזהה ההפעלה 101 וגודל היומן מהקלט
// - רשמו ביומן שתי פעילויות עם הקודים 1 ו-2
// - הדפיסו "Stack session activities: <count>"
// כשהבלוק מסתיים, ה-destructor פועל אוטומטית
// TODO: אחרי הבלוק, הדפיסו "Stack session destroyed"
// TODO: צרו Session המוקצה בערימה באמצעות new
// - שם משתמש: "Guest", מזהה הפעלה: 202, גודל יומן: 3
// - רשמו ביומן פעילות אחת עם הקוד 5
// - הדפיסו "Heap session activities: <count>"
// - מחקו את ההפעלה
// TODO: לאחר המחיקה, הדפס "Heap session destroyed"
return 0;
}
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות OOP
קבצים חיצונייםבנייה וקומפילציה של C++קובצי Header וקובצי מקורמרחבי שמות והיקףמבוא ל-OOP ב-C++מחלקות לעומת אובייקטיםמצביע thisמתודות (פונקציות איבר)תכונות (חברי נתונים)יסודות הבנאים וההורסיםסיכום – מחשבון פשוט4מאפייני מחלקה
חברי מופע לעומת חברים סטטייםגטרים וסטריםפונקציות איבר קבועותמילת המפתח mutableמתודות ומשתנים סטטייםפונקציות ומחלקות ידידותסיכום – מנהל חשבונות בנק7ירושה
ירושה בסיסיתרמות גישה בירושהסדר הקריאות לבנאי ולמפרקדריסת מתודותפונקציות וירטואליות ו־VTableירושה מרובהירושה וירטואליתסיכום – היררכיית עובדים2ניהול זיכרון
זיכרון Stack לעומת Heapמצביעים והפניותזיכרון דינמי (new/delete)מצביעים חכמים ב-C++RAII ב-C++סיכום – מנהל מערכים דינמיים5כימוס
מצייני גישה ב-C++מצייני גישה לעומקהסתרת מידעמבנה לעומת מחלקהמחלקות מקוננות ופנימיותסיכום – מערכת רשומות תלמידים8רב־צורתיות
רב־צורתיות בזמן קומפילציה לעומת בזמן ריצההעמסת פונקציותפונקציות וירטואליות — מבט נוסףפונקציות וירטואליות טהורותמחלקות מופשטותתכנון ממשקים ב־C++המרה דינמית ו־RTTIסיכום — מחשבון צורות3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין