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

RAII في C++

جزء من قسم Object Oriented Programming في رحلة C++ على Coddy — الدرس 16 من 104.

RAII (Resource Acquisition Is Initialization) هو أسلوب أساسي في لغة C++ حيث ترتبط إدارة الموارد بدورة حياة الكائن (object lifetime). الفكرة بسيطة: الحصول على الموارد في المنشئ (constructor)، وتحريرها في الهادم (destructor).

عندما يتم إنشاء كائن، فإنه يكتسب أي موارد يحتاج إليها. وعندما يخرج الكائن عن النطاق، يقوم المدمر (destructor) الخاص به بتحرير تلك الموارد تلقائيًا. يضمن ذلك عملية التنظيف حتى في حالة حدوث استثناءات.

class FileHandler {
    std::FILE* file;
public:
    FileHandler(const char* filename) {
        file = std::fopen(filename, "r");  // الحصول على المورد
    }
    
    ~FileHandler() {
        if (file) std::fclose(file);       // تحرير المورد
    }
};

مع RAII، لن تنسى أبداً القيام بعملية التنظيف:

void processFile() {
    FileHandler handler("data.txt");  // تم الاستحواذ على المورد
    // العمل مع الملف...
    // إذا تم طرح استثناء هنا، فسيظل المدمر (destructor) يعمل!
}   // يتم تحرير المورد تلقائياً

تعد المؤشرات الذكية المثال المثالي لـ RAII قيد التنفيذ. حيث يستحوذ unique_ptr على ملكية ذاكرة الكومة (heap memory) في المنشئ الخاص به (constructor) ويقوم بحذفها في المدمر (destructor). لقد كنت تستخدم RAII بالفعل دون أن تدرك ذلك!

ينطبق RAII على أي مورد: الذاكرة، أو مقابض الملفات، أو اتصالات الشبكة، أو الموتكسات (mutexes)، أو اتصالات قواعد البيانات. من خلال تغليف الموارد في كائنات (objects)، تضمن لغة C++ عملية تنظيف حتمية وتقضي على فئات كاملة من الأخطاء البرمجية مثل التسريبات (leaks) والمقابض المعلقة (dangling handles).

challenge icon

التحدي

سهل

لنقم ببناء مدير اتصالات يوضح مفهوم RAII من خلال التعامل تلقائيًا مع دورة حياة الاتصالات الشبيهة بالشبكة. عندما يتم إنشاء اتصال، يجب أن "يفتح"، وعندما يخرج عن النطاق (scope)، يجب أن "يغلق" تلقائيًا — دون الحاجة إلى تنظيف يدوي!

ستقوم بإنشاء ملفين لتنظيم الكود الخاص بك:

  • Connection.h: قم بتعريف فئة Connection تدير مورد اتصال باستخدام مبادئ RAII. يجب أن تحتوي الفئة على:
    • سمة connectionId خاصة (string) لتحديد الاتصال
    • سمة isOpen خاصة (bool) لتتبع حالة الاتصال
    • منشئ (constructor) يأخذ معرف اتصال، ويضبط isOpen على true، ويطبع "Opening connection: <id>"
    • مدمر (destructor) يتحقق مما إذا كان الاتصال مفتوحًا، ويغلقه عن طريق ضبط isOpen على false، ويطبع "Closing connection: <id>"
    • دالة send(string message) تطبع "[<id>] Sending: <message>" إذا كان الاتصال مفتوحًا
    • دالة getId() تعيد معرف الاتصال
  • main.cpp: وضح كيف يضمن RAII التنظيف التلقائي من خلال قراءة معرف اتصال ورسالة من المدخلات. ثم:
    • أنشئ نطاقًا (scope) باستخدام الأقواس المتعرجة { }
    • داخل ذلك النطاق، أنشئ كائن Connection على المكدس (stack) باستخدام المعرف المدخل
    • استخدم الاتصال لإرسال الرسالة المدخلة
    • اترك النطاق ينتهي (يجب أن يغلق الاتصال تلقائيًا)
    • بعد النطاق، اطبع "Connection out of scope"

يكمن جمال RAII في أنك لست بحاجة إلى تذكر إغلاق الاتصال — فالمُدمر يتولى الأمر تلقائيًا عندما يخرج الكائن عن النطاق. يمنع هذا النمط تسرب الموارد حتى لو حدث شيء غير متوقع في الكود الخاص بك.

قم بتضمين ملف الرأس الخاص بك في main.cpp باستخدام #include "Connection.h".

ورقة مرجعية

RAII (Resource Acquisition Is Initialization) هو أسلوب برمجي في لغة C++ حيث يتم ربط إدارة الموارد بدورة حياة الكائن. يتم الحصول على الموارد في المنشئ (constructor) وتحريرها في المدمر (destructor).

عندما يخرج الكائن عن النطاق (out of scope)، يقوم المدمر الخاص به بتحرير الموارد تلقائياً، مما يضمن التنظيف حتى في حالة حدوث استثناءات:

class FileHandler {
    std::FILE* file;
public:
    FileHandler(const char* filename) {
        file = std::fopen(filename, "r");  // Acquire resource
    }
    
    ~FileHandler() {
        if (file) std::fclose(file);       // Release resource
    }
};

يضمن RAII التنظيف التلقائي:

void processFile() {
    FileHandler handler("data.txt");  // Resource acquired
    // Work with file...
    // If exception thrown, destructor still runs!
}   // Resource automatically released

تعد المؤشرات الذكية مثل unique_ptr أمثلة على RAII. فهي تحجز ذاكرة الكومة (heap memory) في المنشئ وتحذفها في المدمر.

ينطبق RAII على أي مورد: الذاكرة، مقابض الملفات (file handles)، اتصالات الشبكة، كائنات الـ mutex، أو اتصالات قاعدة البيانات. فهو يضمن التنظيف الحتمي ويقضي على الأخطاء البرمجية مثل تسرب الذاكرة (leaks) والمقابض المعلقة (dangling handles).

جرّب بنفسك

#include <iostream>
#include <string>
#include "Connection.h"

using namespace std;

int main() {
    string connectionId;
    string message;
    
    // قراءة المدخلات
    cin >> connectionId;
    cin.ignore();
    getline(cin, message);

    // TODO: إنشاء نطاق (scope) باستخدام الأقواس المتعرجة { }
    // داخل النطاق:
    //   - إنشاء كائن Connection على الـ stack باستخدام connectionId
    //   - استخدام الاتصال لإرسال message
    // اترك النطاق ينتهي (سيتم استدعاء الـ destructor تلقائياً)

    // TODO: بعد انتهاء النطاق، اطبع "Connection out of scope"

    return 0;
}
quiz iconاختبر نفسك

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

جميع دروس Object Oriented Programming