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).
التحدي
سهللنقم ببناء مدير اتصالات يوضح مفهوم 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"
- أنشئ نطاقًا (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;
}
يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.
جميع دروس Object Oriented Programming
1أساسيات الـ OOP
الملفات الخارجيةبناء وتجميع (Compilation) لغة C++ملفات الـ Header وملفات الـ Sourceالـ Namespaces والـ Scopeمقدمة في الـ OOP في C++الـ Classes مقابل الـ Objectsالمؤشر 'this'الـ Methods (الدوال الأعضاء)الـ Attributes (بيانات الأعضاء)أساسيات الـ Ctors والـ Dtorsمراجعة - آلة حاسبة بسيطة4خصائص الـ Class
أعضاء الـ Instance مقابل الـ Staticدوال الـ Getters والـ Settersدوال الأعضاء الثابتة (Const)الكلمة المفتاحية Mutableالدوال والمتغيرات الساكنة (Static)الدوال والفئات الصديقة (Friend)مراجعة - مدير الحساب البنكي7الوراثة
الوراثة الأساسيةمستويات الوصول في الوراثةترتيب استدعاء الـ Ctor والـ Dtorإعادة تعريف الدوال (Method Overriding)الدوال الافتراضية والـ VTableالوراثة المتعددةالوراثة الافتراضيةمراجعة - هيكلية الموظفين10نظرة عامة على STL
نظرة عامة وفلسفة STLحاويات STLالـ Iteratorsخوارزميات STLالـ Functors وتعبيرات Lambdaمراجعة - تكرار الكلمات13أنماط التصميم - الجزء الأول
مقدمة في أنماط التصميمنمط Singletonنمط Factory و Abstract Factoryنمط Builderنمط Observerنمط Strategy2إدارة الذاكرة
ذاكرة Stack مقابل Heapالمؤشرات والمراجعالذاكرة الديناميكية (new/delete)المؤشرات الذكية في C++RAII في C++مراجعة - مدير المصفوفات الديناميكية5التغليف (Encapsulation)
محددات الوصول في C++محددات الوصول بشكل متعمقإخفاء المعلوماتStruct مقابل Classالأصناف المتداخلة والداخليةمراجعة - نظام سجلات الطلاب8تعدد الأشكال (Polymorphism)
تعدد الأشكال: وقت التجميع مقابل وقت التشغيلالتحميل الزائد للدوال (Function Overloading)مراجعة الدوال الافتراضية (Virtual Functions)الدوال الافتراضية البحتة (Pure Virtual Functions)الأصناف المجردة (Abstract Classes)تصميم الواجهات (Interface) في C++التحويل الديناميكي (Dynamic Casting) و RTTIملخص - حاسبة الأشكال11مفاهيم OOP المتقدمة
التركيب مقابل الوراثةالـ Mixins عبر CRTPنمط Pimplمحو النوع (Type Erasure)أصناف Enum والأنواع القويةمعالجة الاستثناءات في OOPتسلسلات الاستثناءات المخصصة14أنماط التصميم - الجزء الثاني
نمط الأمرنمط المحولنمط المزيننمط قالب الطريقةنمط الحالةنمط التركيبRAII كنمط3المنشئات والموادم
المنشئ الافتراضيالمنشئ ذو المعاملاتمنشئ النسخمنشئ النقلقوائم تهيئة المنشئالمنشئات المفوضةتعمق في الموادمقاعدة الثلاثة / الخمسة / الصفرمراجعة - فئة String6تحميل العوامل (Operator Overloading)
مقدمة في تحميل العواملتحميل العوامل الحسابيةتحميل عوامل المقارنةعوامل Streamتحميل عامل التعيينتحميل العوامل [] و ()عوامل تحويل النوعمراجعة - Matrix Class9القوالب
قوالب الدوالقوالب الأصنافتخصيص القوالبالقوالب المتغيرةأساسيات SFINAE و Type Traitsمراجعة - الحاويات العامة