CPP Basics
ניהול חיי המשאבים הוא נושא בסיסי שיש לשלוט בו בC++
.
זאת מכיוון שC++
היא שפה בלתי מנוהלת.
ל-C++
יש Runtime
שמבצע דברים מאוד בסיסיים.
אך אם ברצוננו למחוק זיכרון, לשחרר נעילת קובץ, לשחרר תהליכון רץ - עלינו לעשות זאת לבד.
RAII - Resource Acquisition Is Initialization
בתרגום חופשי ניתן לומר “השגת משאב הוא ביצירתו”.
RAII
משתמש בטכניקה קשירת המשאב לחיי אובייקט.
כפי שידוע חיי אובייקט מוגדר ע”פ ה-Scope
שלו.Scope
מוגדר בעזרת סוגריים מסולסלות { ... }
.
1 | struct A { }; |
יצירת מחלקה
כדי להדגים את היכולת ניצור מחלקה משלנו לניהול חיי זיכרון.
זיכרון מוקצה ע”י המילה השמורה new
:
1 | int* var = new int(5); |
וכדי למחוק אותו נצטרך למחוק בצורה מפורשת:
1 | delete var; |
זה יוצר מגוון בעיות:
- יצירה ומחיקה מפורשת של זיכרון.
- ניהול זיכרון בצורה ידנית.
- כאשר מדובר במשאבים גדולים כך גם ניהול הזיכרון הופך למורכב יותר.
- יכול ליצור דליפות זיכרון.
כדי להקל על יצירת ומחיקה ושימוש לא מפורש ביצירת הזיכרון נבצע נכמס את הפעולה הזו במחלקה.
הבנאי
הטכניקה משתמשת בבנאי כאתחול המשאב - אם יצירת המשאב נכשלת נזרקת שגיאה.
1 | struct EncapsulatePtr |
כאשר נשתמש במשאב נוכל ליצור את המופע של המחלקה:
1 | int main() |
מחיקת המשאב
כדי למחוק את המשאב נשתמש ב-dtor
.
יש להימנע מזריקת שגיאות בעת מחיקת המשאב כדי לא להתעסק בזה בצורה מפורשת.
מחיקת משאבים אמורה להיות נקייה משגיאות ו”חלקה”.
1 | struct EncapsulatePtr |
כעת השימוש בו הוא אוטומטי, לא צריך אפילו לחשוב על מחיקת המשאב:
1 | int main() |
דוגמאות ל-RAII
בספריית C++
הסטנדרטית קיימים דוגמאות לשימוש בקונספט:
std::string
הדוגמא הקלאסית היא מחרוזת.
מאחורי הקלעים קיימת מחרוזת מוקצת מראש למחרוזות קטנות - אך אם אנחנו מקצים מחרוזת ענקית על המחלקה להקצות זיכרון חדש.
1 | int main() |
std::vector
ווקטור הוא לא השם הכי מוצלח למחלקה הזו - שם מוצלח יהיה list
או dynamic list
.
- למרות שקיים גם
std::list
.
וקטור כמו המחרוזת הוא מערך תווים סדרתי - ז”א הזיכרון שמוקצה בו הוא רשימה אחת של בייטים.
1 | int main() |
std::mutex
והדוגמא הכי קלאסית תהיה נעילות אוטומטיות:
1 | std::mutex mutex; |
נעילה תהיה ע”י קריאה למתודות lock
ו-unlock
.
מה לא לעשות:
1 | void Dont() |
מה כן לעשות:
1 | void Do() |
Smart pointers
אי אפשר לדבר על RAII
מבלי להזכיר את ה-Smart pointers
.
במקום לנהל את המצביע לבד או לכתוב מחלקות בשביל זה יש לנו כמה מחלקות שעוזרות לנו:
std::unique_ptr
- מצביע שיש אותו רק פעם אחת.std::shared_ptr
- מצביע השומר את כמות הייחסות למצביע.
זהו סוג של “מנגנון אוטומטי” לאיסוף זיכרון הנותן לנו שליטה על הבעלות על המצביע.
מהסיבה הזו יש קונבנציות המורות על שימוש במצביע נקי רק בשביל “להתייחס” לזיכרון ולא כבעלות לזיכרון.
1 | { |
טכניקת RAII
פופולרית בשפה ויש לשלוט בה כדי לנהל משאבים בצורה חכמה.
השימוש במחלקה עוזרת בכך שניתן לממש עיצובים שונים למחלקות ולבנות היררכיות שייטפלו בכל המשאבים.
גם בירושה או הכלה.
זיכרון לא מנוהל זה זיכרון אבוד :)
תודה על הקריאה!