Menu
Coddy logo textTech

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 הופך ניהול ידני של משאבים, המועד לשגיאות, לניקוי אוטומטי ובטוח.

challenge icon

אתגר

קל

בואו נבנה מנהל מאגר חיבורים באמצעות 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 באמצעות תחומי חיים.

    קראו שני קלטים:

    1. קיבולת המאגר (מספר שלם)
    2. מספר החיבורים המבוקשים (מספר שלם)

    צרו 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;
}
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר C++ אונליין