RAII כתבנית
חלק מהיחידה תכנות מונחה עצמים במסלול ה-C++ של Coddy. שיעור 102 מתוך 104.
RAII (Resource Acquisition Is Initialization) הוא יותר מאשר רק אידיום של C++ — זהו דפוס תכנון רב־עוצמה הקושר את ניהול המשאבים למשך החיים של אובייקטים. כבר ראית RAII עם מצביעים חכמים, אבל הדפוס חל על כל משאב: מזהי קבצים, חיבורי רשת, mutexes או טרנזקציות של מסדי נתונים.
הרעיון המרכזי פשוט: רוכשים משאבים בבנאי ומשחררים אותם במפרק. מכיוון ש־C++ מבטיחה שמפרקים יופעלו כשהאובייקטים יוצאים מטווח החיים שלהם, הניקוי מתבצע אוטומטית — אפילו כשמתרחשות חריגות:
#include <iostream>
#include <fstream>
class FileGuard {
std::ofstream file;
public:
FileGuard(const std::string& filename) : file(filename) {
if (!file.is_open()) {
std::cout << "Failed to open file\n";
}
}
void write(const std::string& text) {
if (file.is_open()) file << text;
}
~FileGuard() {
if (file.is_open()) {
file.close();
std::cout << "File closed automatically\n";
}
}
};
int main() {
{
FileGuard guard("output.txt");
guard.write("Hello RAII");
} // ה־Destructor נקרא כאן - הקובץ נסגר
std::cout << "After scope\n";
}RAII מצטיין בניהול נעילות בקוד מרובה־תהליכונים. std::lock_guard של הספרייה הסטנדרטית פועל לפי התבנית הזו — הוא רוכש mutex בעת יצירתו ומשחרר אותו בעת השמדתו, וכך מונע מבוי סתום שנגרם משכחת שחרור הנעילה.
בעת מימוש מחלקות RAII, חשוב לזכור למחוק או לממש כראוי פעולות העתקה/העברה (כלל החמישה), כדי למנוע שכפול משאבים או שחרור כפול. RAII הופך ניהול ידני של משאבים, המועד לשגיאות, לניקוי אוטומטי ובטוח.
אתגר
קלבואו נבנה מנהל מאגר חיבורים באמצעות RAII כדי לנהל בבטחה חיבורי מסד נתונים. ביישומים אמיתיים, חיבורי מסד נתונים הם משאבים יקרים שיש לרכוש ולשחרר כראוי. תיצרו מעטפת RAII שמבטיחה שהחיבורים תמיד יוחזרו למאגר, גם אם מתרחשות חריגות או שנתיבי הקוד מסתבכים.
תארגנו את הקוד שלכם בשלושה קבצים:
ConnectionPool.h: צרו מאגר חיבורים פשוט שמנהל מספר מוגבל של חיבורים.המחלקה
ConnectionPoolשלכם צריכה לעקוב אחר מספר החיבורים הזמינים (התחילו בקיבולת שמועברת לבנאי). ממשו:acquire(): אם חיבור זמין, הפחיתו את המונה והדפיסוConnection acquired (X available)כאשר X הוא מספר החיבורים שנותרו; החזירוtrueאם הפעולה הצליחה, ו-falseאם אין חיבורים זמיניםrelease(): הגדילו את מספר החיבורים הזמינים והדפיסוConnection released (X available)available(): מחזירה את מספר החיבורים הזמינים הנוכחי
ConnectionGuard.h: בנו את מעטפת ה-RAII שמנהלת בבטחה חיבור יחיד.המחלקה
ConnectionGuardמממשת את תבנית RAII. עליה:- לקבל הפניה ל-
ConnectionPoolבבנאי שלה ולנסות לרכוש חיבור - לשמור האם הרכישה הצליחה
- לספק מתודת
isConnected()כדי לבדוק אם השומר מחזיק חיבור תקף - לשחרר אוטומטית את החיבור בחזרה למאגר בהורס (רק אם נרכש חיבור)
- למחוק את בנאי ההעתקה ואת אופרטור ההשמה בהעתקה כדי למנוע שכפול משאבים (שיקול של Rule of Five)
כאשר ההורס מופעל, אם הוחזק חיבור, הדפיסו
Guard releasing connectionלפני קריאה ל-release במאגר.- לקבל הפניה ל-
main.cpp: הדגימו את הניקוי האוטומטי של RAII באמצעות תחומי חיים.קראו שני קלטים:
- קיבולת המאגר (מספר שלם)
- מספר החיבורים המבוקשים (מספר שלם)
צרו
ConnectionPoolעם הקיבולת הנתונה. לאחר מכן, בתוך תחום מקונן (באמצעות סוגריים מסולסלים), צרו את מספר האובייקטים המבוקש מסוגConnectionGuardושמרו אותם במערך. עבור כל שומר, הדפיסו אם החיבור הצליח:- אם החיבור הצליח:
Guard N: Connected - אם החיבור לא הצליח:
Guard N: Failed to connect
(כאשר N מתחיל ב-1)
לאחר סיום התחום (השומרים נהרסים), הדפיסו
After scope: X connections availableכדי להציג את המצב הסופי של המאגר.
לדוגמה, עם הקלטים 2 ו-3:
Connection acquired (1 available)
Guard 1: Connected
Connection acquired (0 available)
Guard 2: Connected
Guard 3: Failed to connect
Guard releasing connection
Connection released (1 available)
Guard releasing connection
Connection released (2 available)
After scope: 2 connections availableעם הקלטים 3 ו-2:
Connection acquired (2 available)
Guard 1: Connected
Connection acquired (1 available)
Guard 2: Connected
Guard releasing connection
Connection released (2 available)
Guard releasing connection
Connection released (3 available)
After scope: 3 connections availableשימו לב כיצד החיבורים משתחררים אוטומטית כשהשומרים יוצאים מתחום החיים שלהם. אינכם קוראים במפורש ל-release בקוד הראשי שלכם. ההורסים מופעלים בסדר הפוך לסדר הבנייה (השומר האחרון נהרס ראשון), ומובטח שכל חיבור שנרכש יוחזר. זהו הכוח של RAII: ניקוי המשאבים מתבצע אוטומטית ובאמינות, בלי קשר לאופן שבו התחום מסתיים.
נסו בעצמכם
#include <iostream>
#include <vector>
#include "ConnectionPool.h"
#include "ConnectionGuard.h"
using namespace std;
int main() {
// קראו את הקלט
int capacity;
int numConnections;
cin >> capacity;
cin >> numConnections;
// TODO: צרו ConnectionPool עם הקיבולת הנתונה
// TODO: צרו תחום מקונן באמצעות סוגריים מסולסלים
{
// TODO: צרו vector לאחסון אובייקטים מסוג ConnectionGuard
// רמז: תצטרכו להשתמש במצביעים או במצביעים חכמים, מכיוון של־ConnectionGuard
// יש בנאי העתקה שנמחק
// TODO: צרו בלולאה numConnections אובייקטי guard
// עבור כל guard, הדפיסו אחת מהאפשרויות הבאות:
// "Guard N: Connected" או "Guard N: Failed to connect"
// כאשר N מתחיל ב־1
}
// אובייקטי guard נהרסים כאן כשהתחום מסתיים
// TODO: הדפיסו "After scope: X connections available"
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סיכום — מחשבון צורות11מושגי OOP מתקדמים
הרכבה לעומת הורשהMixins באמצעות CRTPאידיום Pimplמחיקת טיפוסיםמחלקות Enum וטיפוסיות חזקהטיפול בחריגות ב-OOPהיררכיות חריגות מותאמות אישית14תבניות עיצוב חלק 2
תבנית הפקודהתבנית המתאםתבנית העיצובתבנית השיטה התבניתיתתבנית המצבתבנית המורכבRAII כתבנית3בנאים והורסים
בנאי ברירת מחדלבנאי עם פרמטריםבנאי העתקהבנאי העברהרשימות אתחול בבנאיםבנאים מאציליםמעמיקים בנושא הורסיםכלל השלושה / החמישה / האפססיכום – מחלקת String6העמסת אופרטורים
מבוא להעמסת אופרטוריםהעמסת אופרטורים אריתמטייםהעמסת אופרטורי השוואהאופרטורי זרםהעמסת אופרטור השמההעמסת האופרטורים [] ו־()אופרטורים להמרת טיפוסיםסיכום – מחלקת Matrix9תבניות
תבניות פונקציהתבניות מחלקההתמחות תבניותתבניות ארגומנטים משתנותיסודות SFINAE ותכונות טיפוסיםסיכום – מכל גנריתרגלו בעצמכם: קומפיילר C++ אונליין