Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

RAII كنمط

جزء من قسم البرمجة كائنية التوجه في رحلة C++ على Coddy. الدرس 102 من 104.

RAII (Resource acquisition هو التهيئة) هو أكثر من مجرد اصطلاح في C++ - إنه نمط تصميم قوي يربط إدارة الموارد بعمر الكائن. لقد رأيت RAII من قبل مع المؤشرات الذكية، لكن هذا النمط ينطبق على أي مورد: مقابض الملفات، واتصالات الشبكة، وعمليات كتم الصوت، أو معاملات قاعدة البيانات.

الفكرة الأساسية بسيطة: acquire الموارد في constructor، وتحريرها في المُدمّر. بما أن 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");
    }  // يُستدعى المُدمِّر هنا - تم إغلاق الملف
    
    std::cout << "After scope\n";
}

تتألق RAII عند إدارة الأقفال في التعليمات البرمجية متعددة الخيوط. تتبع المكتبة القياسية std::lock_guard هذا النمط؛ إذ تستحوذ على كائن mutex عند الإنشاء وتحرره عند التدمير، مما يمنع حالات التعارض الناتجة عن نسيان إلغاء القفل.

عند تنفيذ أصناف RAII، تذكّر أن تقوم إمّا بـ delete أو بتنفيذ عمليات copy/move بشكل صحيح (قاعدة Five) لمنع مشكلات تكرار الموارد أو التحرير المزدوج. يحوّل RAII إدارة الموارد اليدوية المعرّضة للأخطاء إلى تنظيف آمن وتلقائي.

challenge icon

التحدي

سهل

لنبنِ Connection Pool Manager باستخدام RAII لإدارة اتصالات قاعدة البيانات بأمان. في التطبيقات الحقيقية، تُعد اتصالات قاعدة البيانات موارد باهظة التكلفة يجب الحصول عليها وتحريرها بطريقة صحيحة. ستنشئ غلاف RAII يضمن إعادة الاتصالات دائمًا إلى التجمع، حتى عند حدوث استثناءات أو تعقّد مسارات التنفيذ.

ستنظم التعليمات البرمجية عبر ثلاثة ملفات:

  • ConnectionPool.h: Create تجمع اتصال بسيطًا يدير عددًا محدودًا من الاتصالات.

    ينبغي أن يتتبع صنف ConnectionPool عدد الاتصالات available (يبدأ بسعة تُمرر إلى constructor). نفّذ ما يلي:

    • acquire(): إذا كان connection available، فقلّل العدد واطبع Connection acquired (X available) حيث تمثل X العدد المتبقي؛ أعد true عند النجاح، وfalse إذا لم تكن هناك connections available
    • release(): زِد العدد available واطبع Connection released (X available)
    • available(): يعيد العدد current للـ connections available
  • ConnectionGuard.h: ابنِ غلاف RAII يدير connection واحدًا بأمان.

    يجسّد صنف ConnectionGuard نمط RAII. وينبغي أن يقوم بما يلي:

    • يأخذ reference إلى ConnectionPool في constructor الخاص به ويحاول acquire connection
    • يخزّن ما إذا كانت acquisition ناجحة
    • يوفّر method باسم isConnected() للتحقق مما إذا كان الـ guard يحتفظ بـ connection صالح
    • يحرر connection تلقائيًا ويعيده إلى pool في destructor (فقط إذا تم acquire واحد)
    • يحذف copy constructor وcopy assignment لمنع تكرار المورد (اعتبار Rule of Five)

    عند تشغيل destructor، إذا كان هناك connection محتجز، فاطبع Guard releasing connection قبل استدعاء release على pool.

  • main.cpp: أوضح التنظيف التلقائي في RAII من خلال النطاقات.

    اقرأ مدخلين:

    1. سعة pool (عدد صحيح)
    2. عدد الاتصالات المطلوب طلبها (عدد صحيح)

    Create ConnectionPool باستخدام السعة المحددة. ثم داخل نطاق متداخل (باستخدام curly braces)، أنشئ العدد المطلوب من كائنات ConnectionGuard المخزنة في vector. لكل guard، اطبع ما إذا كان قد اتصل بنجاح:

    • إذا كان connected: Guard N: Connected
    • إذا لم يكن connected: Guard N: Failed to connect

    (حيث يبدأ N من 1)

    بعد انتهاء النطاق (وتدمير guards)، اطبع After scope: X connections available لإظهار الحالة النهائية لـ pool.

على سبيل المثال، مع المدخلين 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 للاتصالات تلقائيًا عندما تخرج guards من النطاق. لا تستدعي release بشكل صريح مطلقًا في main code الخاص بك. تعمل destructors بترتيب عكسي للإنشاء (يُدمَّر آخر guard أولًا)، ويُضمن إعادة كل connection تم acquire له. هذه هي قوة 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 N: Connected" or "Guard N: Failed to connect"
        // حيث يبدأ N من 1
        
    }
    // يتم تدمير الحراس هنا عند انتهاء النطاق
    
    // TODO: Print "After scope: X connections available"
    
    return 0;
}
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجه

تدرّب بنفسك: مترجم C++ عبر الإنترنت