يُعدّ الحصول على الموارد عملية تهيئة.
يُعدّ مفهوم "استحواذ الموارد أثناء التهيئة" ( RAII ) [ 1 ] مصطلحًا برمجيًا [ 2 ] يُستخدم في العديد من لغات البرمجة الكائنية التوجه ذات الكتابة الثابتة لوصف سلوك لغة معينة. في RAII، يُعتبر الاحتفاظ بالمورد ثابتًا في الفئة ، ويرتبط بدورة حياة الكائن . يتم تخصيص الموارد (أو الاستحواذ عليها) أثناء إنشاء الكائن (وتحديدًا أثناء التهيئة)، بواسطة الدالة البانية ، بينما يتم تحرير الموارد (إلغاؤها) أثناء تدمير الكائن (وتحديدًا أثناء الإنهاء)، بواسطة الدالة الهدامة . بعبارة أخرى، يجب أن ينجح الاستحواذ على الموارد لكي تنجح التهيئة. وبالتالي، يُضمن الاحتفاظ بالمورد بين انتهاء التهيئة وبدء الإنهاء (يُعتبر الاحتفاظ بالموارد ثابتًا في الفئة)، وأن يبقى محتفظًا به فقط عندما يكون الكائن قيد التشغيل. لذا، إذا لم تكن هناك تسريبات للكائنات، فلن تكون هناك تسريبات للموارد .
يرتبط RAII بشكل أساسي بلغة C++ ، حيث نشأ، ولكنه يرتبط أيضًا بلغات Ada [ 3 ] و Vala [ 4 ] و Rust [ 5 ] . طُوّرت هذه التقنية لإدارة الموارد الآمنة في C++ [ 6 ] خلال الفترة 1984-1989، بشكل رئيسي على يد بيارن ستروستروب وأندرو كونيغ [ 7 ] ، وقد صاغ ستروستروب المصطلح نفسه [ 8 ] .
من الأسماء الأخرى لهذا الأسلوب: اكتساب المُنشئ، تحرير المُدمِّر (CADRe) [ 9 ] ، ويُطلق على أحد أنماط استخدامه اسم إدارة الموارد القائمة على النطاق (SBRM) [ 10 ] . يُستخدم هذا المصطلح الأخير للحالة الخاصة بالمتغيرات التلقائية . يربط RAII الموارد بدورة حياة الكائن، والتي قد لا تتزامن مع دخول النطاق وخروجه. (تجدر الإشارة إلى أن المتغيرات المُخصصة في الذاكرة الحرة لها دورات حياة غير مرتبطة بأي نطاق مُحدد). مع ذلك، يُعد استخدام RAII للمتغيرات التلقائية (SBRM) هو الاستخدام الأكثر شيوعًا.
مثال بلغة C++
يوضح المثال التالي استخدام RAII للوصول إلى الملفات وقفل mutex :
استيراد std ؛باستخدام std :: mutex ؛ باستخدام std :: ofstream ؛ باستخدام std :: runtime_error ؛ باستخدام std :: scoped_lock ؛ باستخدام std :: string ؛void writeToFile ( const string & message ) { // mutex لحماية الوصول إلى الملف (الذي تتم مشاركته بين الخيوط). static mutex m ;// قم بتأمين mutex قبل الوصول إلى الملف. scoped_lock < mutex > lock ( m );// محاولة فتح الملف. ofstream f { "example.txt" }; if ( ! f . is_open ()) { throw runtime_error ( "تعذر فتح الملف" ); }// كتابة الرسالة إلى الملف. std :: println ( f , message );// سيتم إغلاق الملف أولاً عند مغادرة النطاق (بغض النظر عن الاستثناء) // سيتم إلغاء قفل mutex ثانيًا (من مُدمِّر القفل) عند مغادرة النطاق // (بغض النظر عن الاستثناء). }هذا الكود آمن من الاستثناءات لأن لغة C++ تضمن تدمير جميع الكائنات ذات مدة التخزين التلقائية (المتغيرات المحلية) عند نهاية النطاق المحيط بها، بترتيب عكسي لترتيب إنشائها. [ 11 ] وبالتالي، يُضمن استدعاء دوال التدمير لكل من كائن القفل وكائن الملف عند العودة من الدالة، سواءً حدث استثناء أم لا. [ 12 ]
تتيح المتغيرات المحلية إدارة سهلة لموارد متعددة داخل دالة واحدة: يتم تدميرها بترتيب عكسي لترتيب إنشائها، ولا يتم تدمير الكائن إلا إذا تم إنشاؤه بالكامل - أي إذا لم ينتشر أي استثناء من مُنشئه. [ 13 ]
يُسهّل استخدام RAII إدارة الموارد بشكل كبير، ويُقلّل من حجم الكود الإجمالي، ويُساعد على ضمان صحة البرنامج. ولذلك، توصي إرشادات معايير الصناعة باستخدام RAII، [ 14 ] وتتبع معظم مكتبة C++ القياسية هذا الأسلوب. [ 15 ]
فوائد
تتمثل مزايا RAII كتقنية لإدارة الموارد في أنها توفر التغليف، والسلامة الاستثنائية (لموارد المكدس)، والمحلية (فهي تسمح بكتابة منطق الاستحواذ والإفراج بجانب بعضها البعض).
يُوفّر التغليف لأن منطق إدارة الموارد يُعرّف مرة واحدة في الصنف، وليس عند كل استدعاء. كما يُوفّر أمانًا من الاستثناءات لموارد المكدس (الموارد التي تُحرر في نفس النطاق الذي تم الحصول عليها فيه) بربط المورد بدورة حياة متغير المكدس (متغير محلي مُعلن في نطاق مُحدد): إذا حدث استثناء ، وكان هناك معالجة مناسبة للاستثناءات، فإن الكود الوحيد الذي سيتم تنفيذه عند الخروج من النطاق الحالي هو مُدمّرات الكائنات المُعلنة في ذلك النطاق. وأخيرًا، يُوفّر موضعية التعريف بكتابة تعريفات المُنشئ والمُدمّر بجوار بعضها البعض في تعريف الصنف.
لذا، يجب ربط إدارة الموارد بدورة حياة الكائنات المناسبة لتحقيق التخصيص والاستعادة التلقائيين. تُكتسب الموارد أثناء التهيئة، عندما لا يكون هناك احتمال لاستخدامها قبل توفرها، وتُحرر عند تدمير هذه الكائنات، وهو أمر مضمون الحدوث حتى في حالة حدوث أخطاء.
بمقارنة RAII بالبنية finallyالمستخدمة في Java، كتب ستروستروب أن "في الأنظمة الواقعية، هناك عمليات استحواذ على الموارد أكثر بكثير من أنواع الموارد، لذا فإن تقنية "استحواذ الموارد هو التهيئة" تؤدي إلى كود أقل من استخدام بنية "finally". [ 1 ]
باعتبارها خاصية ثابتة في الأصناف، تضمن RAII أن نسخة الكائن التي من المفترض أن تكون قد حصلت على مورد ما قد فعلت ذلك بالفعل. وهذا يلغي الحاجة إلى طرق "إعداد" إضافية لجعل الكائن المُنشأ حديثًا في حالة قابلة للاستخدام (يتم تنفيذ كل هذا العمل في المُنشئ؛ وبالمثل، تتم مهام "إيقاف التشغيل" لتحرير الموارد في مُدمّر الكائن)، والحاجة إلى اختبار النسخ للتحقق من إعدادها بشكل صحيح قبل كل استخدام. [ 16 ]
الاستخدامات النموذجية
يُستخدم تصميم RAII غالبًا للتحكم في أقفال التزامن في التطبيقات متعددة الخيوط . في هذه الحالة، يُحرر الكائن القفل عند تدميره. بدون RAII في هذا السيناريو، يزداد احتمال حدوث حالة جمود ، وتكون منطق قفل التزامن بعيدًا عن منطق تحريره. مع RAII، يتضمن الكود الذي يقفل التزامن منطق تحرير القفل عند خروج التنفيذ من نطاق كائن RAII.
مثال نموذجي آخر هو التفاعل مع الملفات: قد يكون لدينا كائن يمثل ملفًا مفتوحًا للكتابة، حيث يُفتح الملف في الدالة البانية ويُغلق عند انتهاء تنفيذ البرنامج من نطاق الكائن. في كلتا الحالتين، يضمن RAII فقط تحرير المورد المعني بشكل صحيح؛ مع ضرورة توخي الحذر للحفاظ على سلامة البيانات في حالة حدوث استثناءات. إذا لم يكن الكود الذي يُعدّل بنية البيانات أو الملف آمنًا في حالة حدوث استثناءات، فقد يتم إلغاء قفل mutex أو إغلاق الملف مع تلف بنية البيانات أو الملف نفسه.
يمكن التحكم في ملكية الكائنات المُخصصة ديناميكيًا (الذاكرة المُخصصة باستخدام newRAII في لغة C++)، بحيث يتم تحرير الكائن عند تدمير كائن RAII (المُعتمد على المكدس). ولهذا الغرض، تُعرّف مكتبة C++11 القياسية فئات المؤشرات الذكيةstd::unique_ptr للكائنات المملوكة فرديًا std::shared_ptrوللكائنات ذات الملكية المشتركة. وتتوفر فئات مماثلة أيضًا std::auto_ptrفي C++98، boost::shared_ptrوفي مكتبات Boost .
كما يمكن إرسال الرسائل إلى موارد الشبكة باستخدام RAII. في هذه الحالة، سيرسل كائن RAII رسالة إلى مقبس في نهاية الدالة البانية، عند اكتمال تهيئته. وسيرسل أيضًا رسالة في بداية الدالة الهدامة، عندما يكون الكائن على وشك التدمير. يمكن استخدام هذا التركيب في كائن عميل لإنشاء اتصال مع خادم يعمل في عملية أخرى.
التخلص منه
في العديد من اللغات التي تفتقر إلى إدارة الذاكرة المباشرة أو تثبط استخدامها، يتم استخدام آلية مماثلة تسمى "نمط التخلص" لاستدعاء disposeطريقة تقوم بتنظيف الموارد ذات الصلة على الكائن في نهاية النطاق.
ج
بالنسبة لإصدارات لغة C السابقة لإدخال Clang ومجموعة GNU Compiler Collection، يتم تنفيذ السمة كامتداد غير قياسي للغة C. [ 17 ] يوضح ما يلي متغيرًا باستخدام دالة تدمير معينة سيتم استدعاؤها عندما يخرج المتغير عن نطاق التعريف:defer[[gnu::cleanup]]
#include <stdio.h> #include <time.h>void writeLogFile () { const char * logFileName = "logfile.txt" ;[[ gnu :: cleanup ( fclosep )]] FILE * logFile = fopen ( logFileName , "w+" );time_t now = time ( NULL );fprintf ( logFile , "بدء تسجيل الأحداث في %s في %s" , filename , ctime ( & now )); }في هذا المثال، يقوم المترجم بترتيب fclosepاستدعاء الدالة logFileقبل writeLogFileإرجاع القيمة.
لغة سي++
في لغة C++، يتم التخلص من العناصر مباشرةً باستخدام دالة التدميرX . في C++، تستدعي الفئة دالة التدمير الخاصة بها تلقائيًا ~X()عند الوصول إلى نهاية نطاقها. نمط التخلص هذا يُعادل نمط RAII في C++.
استيراد std ؛باستخدام std :: ifstream ؛ باستخدام std :: string ؛void readFile () { if ( ifstream reader { "story.txt" }; reader ) { string line ; while ( std :: getline ( reader , line )) { std :: println ( "{}" , line ); } } else { std :: println ( stderr , "فشل فتح الملف" ); } // يتم تدمير القارئ بعد كتلة if/else }سي شارب
تتضمن لغة C#using كتلة -with-resources، والتي يمكن استخدامها إذا كان الكائن ينفذ واجهة معينة System.IDisposable. ستستدعي هذه Dispose()الكتلة دالة في نهاية المورد.
باستخدام System ؛ باستخدام System.IO ؛باستخدام ( StreamReader reader = new StreamReader ( "story.txt" )) { string line ; while ( ( line = reader.ReadLine ( )) != null ) { Console.WriteLine ( line ) ; } } // يتم التخلص من قارئ البيانات تلقائيًا هناجافا
تتضمن لغة جافاtry كتلة -with-resources، والتي يمكن استخدامها إذا كان الكائن ينفذ واجهة معينة java.lang.AutoCloseable. ستستدعي هذه close()الكتلة دالة في نهاية المورد.
استيراد java.io.BufferedReader ؛ استيراد java.io.FileReader ؛ استيراد java.io.IOException ؛try ( BufferedReader reader = new BufferedReader ( new FileReader ( " story.txt" ))) { String line ; while ( ( line = reader.readLine ( ) ) ! = null ) { System.out.println ( line ) ; } } catch ( IOException e ) { e.printStackTrace ( ) ; }بايثون
تتميز لغة بايثونwith بوجود كتلة، والتي يمكن استخدامها إذا كان الكائن ينفذ __enter__طرقًا __exit__معينة.
with open ( "story.txt" , "r" ) as file : print ( file.readline ( ) ) # طباعة السطر الأول في الملف file2يُستخدم هذا أيضًا لإدارة الموارد مثل الأقفال .
من مكتبة threading استورد Lockbalance_lock : Lock = Lock () with balance_lock : # قسم حرج: قم بتحديث رصيد الحساب هنا...الصدأ
تتيح لغة Rust تعريف منطق تنظيف مخصص، إذا كان الكائن يُنفذ واجهة معينة std::ops::Drop، مما يستدعي drop()دالة بعد خروج الكائن من النطاق. ويمكن أيضًا استدعاء هذه الدالة يدويًا std::mem::drop().
القيود
لا تعمل خوارزمية RAII إلا مع الموارد التي يتم الحصول عليها وتحريرها (مباشرةً أو غير مباشرةً) بواسطة الكائنات المُخصصة في الذاكرة المكدسة، حيث يكون عمر الكائن ثابتًا ومحددًا بدقة. أما الكائنات المُخصصة في الذاكرة الديناميكية والتي تحصل على الموارد وتُحررها بنفسها، فهي شائعة في العديد من لغات البرمجة، بما في ذلك C++. تعتمد RAII على حذف الكائنات المُخصصة في الذاكرة الديناميكية ضمنيًا أو صراحةً على طول جميع مسارات التنفيذ الممكنة، وذلك لتفعيل مُدمر تحرير الموارد (أو ما يُعادله). [ 18 ] : 8:27 يُمكن تحقيق ذلك باستخدام مؤشرات ذكية لإدارة جميع كائنات الذاكرة الديناميكية، مع استخدام مؤشرات ضعيفة للكائنات ذات المراجع الدورية.
في لغة C++، لا يُضمن فكّ تكديس الاستثناءات إلا إذا تمّت معالجته في مكان ما. والسبب في ذلك هو أنه "إذا لم يُعثر على معالج مطابق في البرنامج، يتم استدعاء الدالة terminate()؛ أما ما إذا كان سيتم فكّ تكديس الاستثناءات قبل استدعاء terminate() أم لا، فهو أمرٌ تحدده آلية التنفيذ (15.5.1)." (معيار C++03، §15.3/9). [ 19 ] يُعدّ هذا السلوك مقبولاً في العادة، لأن نظام التشغيل يُحرّر الموارد المتبقية، مثل الذاكرة والملفات والمنافذ، عند إنهاء البرنامج.
في مؤتمر Gamelab لعام 2018، ادعى جوناثان بلو أن استخدام RAII يمكن أن يتسبب في تجزئة الذاكرة ، مما قد يؤدي بدوره إلى فقدان البيانات في ذاكرة التخزين المؤقت وانخفاض الأداء بمقدار 100 ضعف أو أكثر . [ 20 ]
عد المراجع
تُدير لغات بيرل وبايثون (في تطبيق CPython ) [ 21 ] وبي إتش بي [ 22 ] دورة حياة الكائنات باستخدام عدّ المراجع ، مما يُتيح استخدام RAII. تُدمّر الكائنات التي لم تعد مُشارًا إليها أو تُنهي عملها وتُحرّر فورًا، لذا يُمكن للمُدمّر أو المُنهي تحرير المورد في ذلك الوقت. مع ذلك، لا يُعدّ هذا الأسلوب مُستحبًا دائمًا في هذه اللغات، ويُنصح بتجنّبه تحديدًا في بايثون (لصالح مُديري السياق والمُنهيات من حزمة weakref ).
مع ذلك، لا ترتبط دورة حياة الكائنات بالضرورة بنطاق محدد، وقد تُدمر الكائنات بشكل غير حتمي أو لا تُدمر على الإطلاق. وهذا يُتيح إمكانية تسريب موارد كان من المفترض تحريرها عند انتهاء نطاق معين. قد لا تُنهي الكائنات المخزنة في متغير ثابت (وخاصةً المتغير العام ) عند انتهاء البرنامج، وبالتالي لا تُحرر مواردها؛ ولا يضمن CPython إنهاء هذه الكائنات، على سبيل المثال. علاوة على ذلك، لن يتم جمع الكائنات ذات المراجع الدائرية بواسطة عداد مراجع بسيط، وستبقى لفترة غير محددة؛ وحتى في حال جمعها (بواسطة آلية جمع بيانات مهملة أكثر تطورًا)، فسيكون وقت التدمير وترتيبه غير حتميين. يوجد في CPython كاشف دورات يكتشف الدورات ويُنهي الكائنات الموجودة فيها، مع العلم أنه قبل الإصدار 3.4 من CPython، لا يتم جمع الدورات إذا كان لأي كائن فيها مُنهي. [ 23 ]
انظر أيضاً
مراجع
- 1 2 ستروستروب، بيارن (30-09-2017). "لماذا لا توفر لغة C++ بنية "finally"؟" . تم الاسترجاع في 09-03-2019 .
- ↑ ساتر، هيرب ؛ ألكسندريسكو، أندريه ( 2005). معايير برمجة لغة C++ . سلسلة C++ المتعمقة. أديسون-ويسلي. ص 24. ISBN 978-0-321-11358-0.
- ↑ "الجوهرة رقم 70: مصطلح قفل النطاق" . AdaCore . تم الاطلاع عليه بتاريخ 21 مايو 2021 .
- ↑ مشروع فالادات. "التدمير" . برنامج تعليمي للغة فالا، الإصدار 0.30 . تم الاطلاع عليه بتاريخ 21 مايو 2021 .
- ↑ "RAII - Rust By Example" . doc.rust-lang.org . تم الاطلاع عليه بتاريخ 22-11-2020 .
- ↑ ستروستروب 1994 ، 16.5 إدارة الموارد، ص 388-89.
- ↑ Stroustrup 1994 ، 16.1 معالجة الاستثناءات: مقدمة ، ص 383-84.
- ↑ ستروستروب 1994 ، ص 389. أطلقت على هذه التقنية اسم "اكتساب الموارد هو التهيئة".
- ↑ آرثر تشايكوفسكي (2012-11-06). "تغيير RAII الرسمي إلى CADRe" . معيار ISO C++ - مقترحات مستقبلية . مجموعات جوجل . تم الاسترجاع في 2019-03-09 .
- ^ تشو ، ألين (2014/10/01). "إدارة الموارد على أساس النطاق (RAII)" . تم الاسترجاع 2019-03-09 .
- ↑ ريتشارد سميث (21 مارس 2017). "مسودة عمل، معيار للغة البرمجة C++" (ملف PDF) . صفحة 151، القسم 9.6 . تاريخ الاطلاع: 7 سبتمبر 2023 .
- ↑ "كيف يمكنني التعامل مع مُدمِّر يفشل؟" . مؤسسة لغة C++ القياسية . تم الاطلاع عليه بتاريخ 9 مارس 2019 .
- ↑ ريتشارد سميث (21 مارس 2017). "مسودة عمل، معيار للغة البرمجة C++" (ملف PDF) . تم الاطلاع عليه بتاريخ 9 مارس 2019 .
- ^ ستروستروب، بيارن ؛ سوتر ، هيرب (2020-08-03). "المبادئ التوجيهية الأساسية لـ C++" . تم الاسترجاع 2020-08-15 .
- ↑ "لديّ الكثير من كتل try؛ ماذا يمكنني أن أفعل حيال ذلك؟" . مؤسسة لغة C++ القياسية . تم الاسترجاع في 9 مارس 2019 .
- ↑ RAII على cppreference.com
- ↑ "تحديد خصائص المتغيرات" . باستخدام مجموعة مترجمات جنو (GCC) . مشروع جنو . تم الاسترجاع في 9 مارس 2019 .
- ↑ وايمر، ويستلي؛ نيكولا، جورج سي. (2008). "الحالات الاستثنائية وموثوقية البرنامج" (ملف PDF) . معاملات ACM في لغات البرمجة والأنظمة . المجلد 30، العدد 2.
- ↑ ildjarn (2011-04-05). "RAII وفكّ تسلسل المكدس" . Stack Overflow . تم الاسترجاع في 2019-03-09 .
- ↑ Gamelab2018 - قرارات جون بلو التصميمية بشأن إنشاء لغة جاي، وهي لغة جديدة لمبرمجي الألعاب، على يوتيوب
- ↑ "توسيع بايثون باستخدام لغة C أو C++: إحصاءات المراجع" . توسيع وتضمين مترجم بايثون . مؤسسة برمجيات بايثون . تم الاسترجاع في 9 مارس 2019 .
- ↑ هوبز (2011-02-08). "هل يدعم PHP نمط RAII؟ وكيف؟" . تم الاطلاع عليه بتاريخ 2019-03-09 .
- ↑ "gc — واجهة جامع البيانات المهملة" . مكتبة بايثون القياسية . مؤسسة برمجيات بايثون . تم الاسترجاع في 9 مارس 2019 .
للمزيد من القراءة
- ستروستروب، بيارن (1994). تصميم وتطور لغة C++ . أديسون-ويسلي. رمز Bibcode : 1994dec..book.....S . ISBN 978-0-201-54330-8.
روابط خارجية
- فصل نموذجي: " المأزق رقم 67: عدم استخدام عملية الحصول على الموارد هو التهيئة " بقلم ستيفن سي. ديوهيرست
- مقابلة: " محادثة مع بيارن ستروستروب " بقلم بيل فينيرز
- مقال بعنوان: " قانون الاثنين الكبيرين " بقلم بيورن كارلسون وماثيو ويلسون
- مقال بعنوان: " تطبيق مبدأ 'اكتساب الموارد هو التهيئة' " بقلم داني كاليف
- مقال: " RAII والكائنات الديناميكية والمصانع في لغة C++ " بقلم رولاند بيبينجر
- معهد أبحاث الراديو الأمريكي في دلفي: " نبذة مختصرة عن معهد أبحاث الراديو الأمريكي في دلفي " بقلم باري كيلي
- دليل: RAII في لغة C++ من W3computing
- البرمجة الكائنية التوجه
- أنماط تصميم البرمجيات
- مصطلحات البرمجة
- لغة سي++
