نسخ الدلالات وإدارة الموارد في C ++


في C ++ ، يجب أن يقرر المبرمج كيف سيتم تحرير الموارد المستخدمة ؛ لا توجد أدوات تلقائية مثل جامع القمامة. تناقش المقالة الحلول الممكنة لهذه المشكلة ، وتدرس بالتفصيل المشاكل المحتملة ، بالإضافة إلى عدد من القضايا ذات الصلة.




جدول المحتويات




مقدمة


إدارة الموارد هي شيء يجب على مبرمج C ++ القيام به طوال الوقت. تتضمن الموارد كتل الذاكرة وكائنات OS kernel وأقفال متعددة الخيوط واتصالات الشبكة واتصالات قاعدة البيانات وأي كائن تم إنشاؤه في الذاكرة الديناميكية فقط. يتم الوصول إلى المورد من خلال واصف ، وعادة ما يكون نوع الواصف مؤشرًا أو أحد الأسماء المستعارة الخاصة به ( HANDLE ، إلخ) ، وأحيانًا كله (واصفات ملف UNIX). بعد استخدام المورد ، يجب عليك تحريره ، وإلا ستنفد الموارد عاجلاً أم آجلاً من تطبيق لا يطلق الموارد (وربما تطبيقات أخرى). هذه المشكلة حادة جدًا ، يمكننا القول أن إحدى الميزات الرئيسية لـ .NET و Java والعديد من الأنظمة الأساسية الأخرى هي نظام إدارة موارد موحد يعتمد على جمع القمامة.


تؤدي الميزات الموجهة للكائنات في C ++ بشكل طبيعي إلى الحل التالي: تحتوي الفئة التي تدير المورد على واصف المورد كعضو ، وتهيئ الواصف عند التقاط المورد ، وتحرر المورد في المدمر. ولكن بعد بعض التفكير (أو الخبرة) يأتي الفهم بأنه ليس بهذه البساطة. والمشكلة الرئيسية هي دلالات النسخ. إذا كانت الفئة التي تدير المورد تستخدم مُنشئ النسخ الذي تم إنشاؤه بواسطة المترجم الافتراضي ، فعند نسخ الكائن سنحصل على نسختين من مقبض نفس المورد. إذا قام أحد الكائنات بتحرير أحد الموارد ، فسيتمكن الكائن الثاني بعد ذلك من محاولة استخدام أو تحرير المورد الذي تم تحريره بالفعل ، وهو على أي حال غير صحيح ويمكن أن يؤدي إلى ما يسمى بالسلوك غير المحدد ، أي أنه يمكن أن يحدث أي شيء ، على سبيل المثال ، إنهاء غير طبيعي للبرنامج.


لحسن الحظ ، في C ++ ، يمكن للمبرمج التحكم بشكل كامل في عملية النسخ من خلال تحديد مُنشئ النسخ وعامل تعيين النسخ بنفسه ، مما يسمح لنا بحل المشكلة المذكورة أعلاه ، وليس عادةً بطريقة واحدة. يجب أن يكون تنفيذ النسخ مرتبطًا ارتباطًا وثيقًا بآلية تحرير المورد ، وسوف نسميها بشكل جماعي استراتيجية ملكية النسخ. إن ما يسمى بـ "قاعدة الثلاثة الكبار" معروفة جيدًا ، والتي تنص على أنه إذا قام المبرمج بتعريف واحدة على الأقل من العمليات الثلاث - منشئ النسخ أو عامل تعيين النسخ أو المدمر - فيجب عليه تحديد جميع العمليات الثلاث. تحدد إستراتيجيات ملكية النسخ فقط كيفية القيام بذلك. هناك أربع استراتيجيات أساسية لملكية النسخ.



1. استراتيجيات ملكية النسخ الأساسية


قبل التقاط المورد أو بعد إصداره ، يجب أن يأخذ الواصف قيمة خاصة تشير إلى أنه غير مرتبط بالمورد. عادة ما يكون هذا صفرًا ، وأحيانًا -1 ، يلقي إلى نوع واصف. على أي حال ، سيطلق على هذا الوصف صفراً. يجب أن يتعرف الفصل الدراسي الذي يدير المورد على واصف فارغ ولا يحاول استخدام المورد أو تحريره في هذه الحالة.



1.1. استراتيجية حظر النسخ


هذه هي أبسط استراتيجية. في هذه الحالة ، يُمنع ببساطة نسخ وتعيين نسخ الصف. المدمر يحرر المورد الملتقط. في C ++ ، لحظر النسخ ليس صعبًا ، يجب على الفصل أن يعلن ، لكن لا يحدد ، مُنشئ النسخة المغلقة وعامل تعيين النسخ.


 class X { private:    X(const X&);    X& operator=(const X&); // ... }; 

تم إحباط محاولات النسخ من قبل المترجم والرابط.


يوفر معيار C ++ 11 بنية خاصة لهذه الحالة:


 class X { public:    X(const X&) = delete;    X& operator=(const X&) = delete; // ... }; 

بناء الجملة هذا هو أكثر بصرية ويعطي رسائل مفهومة أكثر للمترجم عند محاولة النسخ.


في الإصدار السابق من المكتبة القياسية (C ++ 98) ، استخدمت فئات تدفقات الإدخال / الإخراج ( std::fstream ، وما إلى ذلك) استراتيجية حظر النسخ ، وفي Windows ، العديد من الفئات من MFC ( CFile ، CEvent ، CMutex ، وما إلى ذلك). في المكتبة القياسية C ++ 11 ، تستخدم بعض الفئات هذه الإستراتيجية لدعم التزامن متعدد الخيوط.



1.2. استراتيجية الملكية الحصرية


في هذه الحالة ، عند تنفيذ النسخ والتعيين ، ينتقل واصف المورد من الكائن المصدر إلى الكائن الهدف ، أي أنه يبقى في نسخة واحدة. بعد النسخ أو التعيين ، يحتوي الكائن المصدر على واصف فارغ ولا يمكنه استخدام المورد. المدمر يحرر المورد الملتقط. تُستخدم أيضًا مصطلحات الملكية الحصرية أو الصارمة [Josuttis] لهذه الاستراتيجية ؛ ويستخدم Andrei Alexandrescu مصطلح النسخ المدمر. في C ++ 11 ، يتم ذلك على النحو التالي: يحظر النسخ المنتظم وتعيين النسخ كما هو موضح أعلاه ، ويتم تنفيذ دلالات الحركة ، أي يتم تحديد مُنشئ النقل وعامل تعيين النقل. (المزيد عن دلالات الحركة لاحقًا.)


 class X { public:    X(const X&) = delete;    X& operator=(const X&) = delete;    X(X&& src) noexcept;    X& operator=(X&& src) noexcept; // ... }; 

وبالتالي ، يمكن اعتبار استراتيجية الملكية الحصرية امتدادًا لاستراتيجية حظر النسخ.


في المكتبة القياسية C ++ 11 ، تستخدم هذه الإستراتيجية المؤشر الذكي std::unique_ptr<> وبعض الفئات الأخرى ، على سبيل المثال: std::thread و std::unique_lock<> ، بالإضافة إلى الفئات التي استخدمت سابقًا استراتيجية حظر النسخ ( std::fstream ، وما إلى ذلك). في نظام التشغيل Windows ، بدأت فئات MFC التي استخدمت سابقًا استراتيجية حظر النسخ في استخدام إستراتيجية الملكية الحصرية ( CFile و CEvent و CMutex وما إلى ذلك).



1.3. استراتيجية النسخ العميق


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


لا تنطبق هذه الاستراتيجية على جميع الموارد. يمكن تطبيقه على الموارد المرتبطة بمخزن الذاكرة المؤقت ، مثل السلاسل ، ولكن ليس من الواضح جدًا كيفية تطبيقه على كائنات نواة نظام التشغيل مثل الملفات ، وكائنات المزامنة ، وما إلى ذلك.


يتم استخدام استراتيجية النسخ العميق في جميع أنواع سلاسل الكائن ، std::vector<> والحاويات الأخرى للمكتبة القياسية.



1.4. استراتيجية الملكية المشتركة


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


غالبًا ما تستخدم المؤشرات الذكية استراتيجيات الملكية المشتركة ، ومن الطبيعي أيضًا استخدامها في الموارد الثابتة. يطبق المؤشر الذكي std::shared_ptr<> هذه الإستراتيجية في مكتبة C ++ 11 القياسية.



2. استراتيجية النسخ العميق - المشاكل والحلول


خذ بعين الاعتبار قالبًا لوظيفة تبادل الحالة للكائنات من النوع T في المكتبة القياسية C ++ 98.


 template<typename T> void swap(T& a, T& b) {    T tmp(a);    a = b;    b = tmp; } 

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



2.1. نسخ في السجل


يمكن اعتبار النسخة عند الكتابة (COW) ، والتي تسمى أيضًا النسخة المؤجلة ، على أنها محاولة للجمع بين إستراتيجية النسخ العميق وإستراتيجية الملكية المشتركة. في البداية ، عند نسخ كائن ، يتم نسخ واصف المورد ، بدون المورد نفسه ، وبالنسبة للمالكين ، يصبح المورد مشتركًا وقراءة فقط ، ولكن بمجرد أن يحتاج بعض المالك إلى تعديل المورد المشترك ، يتم نسخ المورد ثم يعمل هذا المالك مع نسخة. تنفيذ COW يحل مشكلة تبادل الدولة: لا يتم تخصيص موارد إضافية ونسخ. يعد استخدام COW أمرًا شائعًا عند تنفيذ السلاسل ؛ على سبيل المثال ، CString (MFC ، ATL). يمكن العثور على مناقشة حول الطرق الممكنة لتنفيذ COW والقضايا الناشئة في [Meyers1] ، [Sutter]. اقترح [Guntheroth] تنفيذ COW باستخدام std::shared_ptr<> . توجد مشاكل عند تنفيذ COW في بيئة متعددة الخيوط ، ولهذا السبب يُحظر استخدام COW للسلاسل في مكتبة C ++ 11 القياسية ، انظر [Josuttis] ، [Guntheroth].


يؤدي تطوير فكرة COW إلى مخطط إدارة الموارد التالي: المورد غير قابل للتغيير وإدارته بواسطة الكائنات باستخدام استراتيجية الملكية المشتركة ، إذا لزم الأمر ، لتغيير المورد ، يتم إنشاء مورد جديد معدّل بشكل مناسب ، ويتم إرجاع كائن مالك جديد. يستخدم هذا المخطط للسلاسل والكائنات غير القابلة للتغيير على منصات .NET و Java. في البرمجة الوظيفية ، يتم استخدامه لهياكل البيانات الأكثر تعقيدًا.



2.2. تحديد وظيفة تبادل الدولة لفئة


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


1. حدد في الفصل وظيفة العضو Swap() (الاسم غير مهم) التي تنفذ تبادل الدول.


 class X { public:    void Swap(X& other) noexcept; // ... }; 

يجب التأكد من أن هذه الوظيفة لا ترمي الاستثناءات ؛ في C ++ 11 ، يجب الإعلان عن هذه الوظائف على أنها لا noexcept .


2. في نفس مساحة الاسم مثل الفئة X (عادة في نفس ملف الرأس) ، قم بتعريف وظيفة swap() غير العضو) المجانية على النحو التالي (الاسم والتوقيع أساسيان):


 inline void swap(X& a, X& b) noexcept { a.Swap(b); } 

بعد ذلك ، ستستخدمه خوارزميات المكتبة القياسية ، وليس std::swap() . يوفر هذا آلية تسمى البحث المعتمد على الوسيطة (ADL). لمعرفة المزيد عن ADL ، انظر [Dewhurst1].


في مكتبة C ++ القياسية ، تقوم جميع الحاويات والمؤشرات الذكية وكذلك الفئات الأخرى بتنفيذ وظيفة تبادل الحالة كما هو موضح أعلاه.


عادة ما يتم تعريف وظيفة عضو Swap() بسهولة: من الضروري تطبيق عملية تبادل الحالة بشكل تسلسلي على قواعد البيانات والأعضاء إذا كانوا يدعمونها ، و std::swap() خلاف ذلك.


الوصف أعلاه مبسط إلى حد ما ، ويمكن العثور على وصف أكثر تفصيلاً في [Meyers2]. يمكن أيضًا العثور على مناقشة للقضايا المتعلقة بوظيفة تبادل الدولة في [Sutter / Alexandrescu].


يمكن أن تعزى وظيفة تبادل الدولة إلى واحدة من العمليات الأساسية للفئة. باستخدامه ، يمكنك تحديد العمليات الأخرى بأمان. على سبيل المثال ، يتم تعريف عامل تعيين النسخ من خلال النسخ و Swap() النحو التالي:


 X& X::operator=(const X& src) {    X tmp(src);    Swap(tmp);    return *this; } 

هذا القالب يسمى لغة النسخ والتبادل أو لغة Herb Sutter ، لمزيد من التفاصيل انظر [Sutter]، [Sutter / Alexandrescu]، [Meyers2]. يمكن تطبيق تعديله لتطبيق دلالات النزوح ، انظر الأقسام 2.4 ، 2.6.1.



2.3. إزالة النسخ الوسيطة بواسطة المترجم


فكر في الفصل


 class X { public:    X(/*  */); // ... }; 

والوظيفة


 X Foo() { // ...    return X(/*  */); } 

مع نهج مباشر ، يتم تحقيق العائد من الدالة Foo() عن طريق نسخ مثيل X لكن المترجمين قادرون على إزالة عملية النسخ من التعليمات البرمجية ، يتم إنشاء الكائن مباشرة عند نقطة الاتصال. وهذا ما يسمى تحسين القيمة المرجعة (RVO). تم استخدام RVO من قبل مطوري المترجم لبعض الوقت ويتم إصلاحه حاليًا في معيار C ++ 11. على الرغم من أن المترجم يتخذ القرار بشأن RVO ، يمكن للمبرمج كتابة التعليمات البرمجية بناءً على استخدامه. للقيام بذلك ، من المستحسن أن يكون للدالة نقطة إرجاع واحدة وأن نوع التعبير الذي تم إرجاعه يطابق نوع القيمة المرجعة للدالة. في بعض الحالات ، يُنصح بتعريف مُنشئ مغلق خاص يسمى "مُنشئ حسابي" ، لمزيد من التفاصيل انظر [Dewhurst2]. تمت مناقشة RVO أيضًا في [Meyers3] و [Guntheroth].


يمكن للمجمعين حذف النسخ الوسيطة في حالات أخرى.



2.4. تنفيذ دلالات النزوح


يتألف تطبيق دلالات الحركة من تعريف مُنشئ تحريك يحتوي على معلمة من النوع rvalue-reference إلى المصدر وعامل تعيين النقل بنفس المعلمة.


في مكتبة C ++ 11 القياسية ، يتم تعريف قالب وظيفة تبادل الحالة على النحو التالي:


 template<typename T> void swap(T& a, T& b) {    T tmp(std::move(a));    a = std::move(b);    b = std::move(tmp); } 

وفقًا لقواعد حل الحمل الزائد للوظائف التي تحتوي على معلمات من نوع مرجع rvalue (انظر الملحق أ) ، في حالة عندما يكون للنوع T مُنشئ متحرك وعامل تعيين متحرك ، سيتم استخدامها ، ولن يكون هناك تخصيص للموارد المؤقتة والنسخ. خلاف ذلك ، سيتم استخدام منشئ النسخ وعامل تعيين النسخ.


يؤدي استخدام دلالات الانتقال إلى تجنب إنشاء نسخ مؤقتة في سياق أوسع بكثير من وظيفة تبادل الحالة الموضحة أعلاه. تنطبق دلالات الحركة على أي قيمة rvalue ، أي قيمة مؤقتة غير مسماة ، وكذلك على القيمة المرجعة للدالة إذا تم إنشاؤها محليًا (بما في ذلك lvalue) ، ولم يتم تطبيق RVO. في جميع هذه الحالات ، من المضمون أنه لا يمكن استخدام الكائن المصدر بأي شكل من الأشكال بعد النقل. تنطبق دلالات النقل أيضًا على قيمة lvalue التي يتم تطبيق تحويل std::move() عليها. ولكن في هذه الحالة ، يكون المبرمج مسؤولاً عن كيفية استخدام الكائنات المصدر بعد النقل (مثال std::swap() ).


تمت إعادة تصميم مكتبة C ++ 11 القياسية مع مراعاة دلالات الحركة. أضافت العديد من الفئات مُنشئ تحريك وعامل تعيين نقل ، بالإضافة إلى وظائف الأعضاء الأخرى ، مع معلمات من النوع rvalue المرجعي. على سبيل المثال ، يحتوي std::vector<T> على نسخة من التحميل الزائد void push_back(T&& src) . كل هذا يسمح في كثير من الحالات بتجنب إنشاء نسخ مؤقتة.


لا يؤدي تطبيق دلالات الحركة إلى إلغاء تعريفات وظيفة تبادل الحالة لفئة. يمكن أن تكون وظيفة تبادل الحالة المحددة بشكل خاص أكثر كفاءة من المعيار std::swap() . علاوة على ذلك ، يتم تعريف مُنشئ الحركة وعامل تعيين النقل بسهولة بالغة باستخدام وظيفة العضو لتبادل الدول على النحو التالي (اختلاف لغة النسخ والصرف):


 class X { public:    X() noexcept {/*    */}    void Swap(X& other) noexcept {/*   */}    X(X&& src) noexcept : X()    {        Swap(src);    }    X& operator=(X&& src) noexcept    {        X tmp(std::move(src)); //         Swap(tmp);        return *this;    } // ... }; 

منشئ الحركة وعامل تعيين النقل هي وظائف الأعضاء التي يكون من المرغوب فيها للغاية التأكد من أنها لا ترمي الاستثناءات وبالتالي يتم الإعلان عنها على أنها لا noexcept . يتيح لك هذا تحسين بعض عمليات حاويات المكتبة القياسية دون انتهاك الضمانة الصارمة لسلامة الاستثناءات ؛ لمزيد من التفاصيل انظر [Meyers3] و [Guntheroth]. يوفر النموذج المقترح مثل هذا الضمان ، شريطة ألا يقوم المُنشئ الافتراضي ووظيفة العضو في تبادل الدول بإلقاء استثناءات.


يوفر معيار C ++ 11 للمترجم الذي يقوم تلقائيًا بإنشاء مُنشئ متحرك وعامل تعيين متحرك. للقيام بذلك ، يجب التصريح باستخدام البناء "=default" .


 class X { public:    X(X&&) = default;    X& operator=(X&&) = default; // ... }; 

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


بشكل عام ، فإن تنفيذ واستخدام دلالات النزوح هو "شيء خفي". يمكن للمترجم تطبيق النسخ حيث يتوقع المبرمج الانتقال. فيما يلي بعض القواعد للقضاء على أو على الأقل تقليل احتمالية حدوث مثل هذا الموقف.


  1. استخدم حظر النسخ إن أمكن.
  2. قم بتعريف مُنشئ التحريك وعامل تعيين النقل noexcept .
  3. تطبيق دلالات الحركة للفئات الأساسية والأعضاء.
  4. قم بتطبيق التحويل std::move() على معلمات دالات مرجع النوع rvalue.

القاعدة 2 نوقشت أعلاه. 4 , rvalue- lvalue (. ). .


 class B { // ...    B(B&& src) noexcept; }; class D : public B { // ...    D(D&& src) noexcept; }; D::D(D&& src) noexcept    : B(std::move(src)) //  {/* ... */} 

, . 6.2.1.



2.5. vs.


, RVO (. 2.3), , . ( ), , . , . C++11 - emplace() , emplace_front() , emplace_back() , . , - — (variadic templates), . , C++11 — .


:


  1. , , .
  2. , , .

, .


 std::vector<std::string> vs; vs.push_back(std::string(3, 'X')); //  vs.emplace_back(3, '7');           //  

std::string , . . , , . , [Meyers3].



2.6. الملخص


, , . - . . — : , . , , , . : , , «» .


: , , .NET Java. , Clone() Duplicate() .


- - , :


  1. .
  2. .
  3. - rvalue-.

.NET Java - , , .NET IClonable . , .



3.


, . - , . , . Windows: , HANDLE , COM-. DuplicateHandle() , CloseHandle() . COM- - IUnknown::AddRef() IUnknown::Release() . ATL ComPtr<> , COM- . UNIX, C, _dup() , .


C++11 std::shared_ptr<> . , , , , , . , . std::shared_ptr<> [Josuttis], [Meyers3].


: - , ( ). ( ) , . std::shared_ptr<> std::weak_ptr<> . . [Josuttis], [Meyers3].


- [Alexandrescu]. ( ) , [Schildt]. , .


( ) [Alger].


-. [Josuttis] [Alexandrescu].


- .NET Java. , , , .



4.


, C++ rvalue- . C++98 std::auto_ptr<> , , , . , , ( ). C++11 rvalue- , , . C++11 std::auto_ptr><> std::unique_ptr<> . , [Josuttis], [Meyers3].


: - ( std::fstream , etc.), ( std::thread , std::unique_lock<> , etc.). MFC , ( CFile , CEvent , CMutex , etc.).



5. —


. , . , , , . , , , ( ) . , , , . ( ) , . , . — . 6.


, - -, « », - . - . , , , , - . «».



6. -


, - . , -. .



6.1.


- . , , :


  1. . , .
  2. .
  3. .

, , , . C++11 .


« » (resource acquisition is initialization, RAII). RAII ( ), ., [Dewhurst1]. «» RAII. , , , (immutable) RAII.



6.2.


, RAII, , , . - , , - . , , , . .



6.2.1.


, , , , :


  1. , .
  2. .
  3. .
  4. .

C++11 , , , . , - clear() , , , . . , shrink_to_fit() , , (. ).


, RAII, , , . , .


 class X { public: // RAII    X(const X&) = delete;            //      X& operator=(const X&) = delete; //      X(/*  */);              //      ~X();                            //   //     X() noexcept;                    //       X(X&& src) noexcept              //      X& operator=(X&& src) noexcept;  //    // ... }; 

.


 X x;                    //  ""  x = X(/*  */); //   x = X(/*  */); //   ,   x = X();                //   

std::thread .


2.4, - . , - - . .


 class X { // RAII // ... public: // ,         X() noexcept;    X(X&& src) noexcept;    X& operator=(X&& src) noexcept;    void Swap(X& other) noexcept; //      void Create(/*  */); //      void Close() noexcept;        //   // ... }; X::X() noexcept {/*    */} 

:


 X::X(X&& src) noexcept : X() {    Swap(src); } X& X::operator=(X&& src) noexcept {    X tmp(std::move(src)); //     Swap(tmp);    return *this; } 

- :


 void X::Create(/*  */) {    X tmp(/*  */); //      Swap(tmp); } void X::Close() noexcept {    X tmp;    Swap(tmp); } 

, , , - . , , , . , .


- « », , . : , , ( ). : , . , : , , . , . [Sutter], [Sutter/Alexandrescu], [Meyers2].


, RAII .



6.2.2.


RAII . , , , , :


  1. , .
  2. .
  3. . , .
  4. .
  5. .

«» RAII, — . , , . 3. . «», .



6.2.3.


— . RAII , . , . , , ( -). - ( -). 6.2.1, .



6.3.


, - RAII, : . , , .



7.


, , , , . - -.


4 -:


  1. .
  2. .
  3. .
  4. .

. , - : , , - .


, . , , -, , .


- . . , (. 6.2.3). , (. 6.2.1). , . , , . , std::shared_ptr<> .



التطبيقات



. Rvalue-


Rvalue- C++ , , rvalue-. rvalue- T T&& .


:


 class Int {    int m_Value; public:    Int(int val) : m_Value(val) {}    int Get() const { return m_Value; }    void Set(int val) { m_Value = val; } }; 

, rvalue- .


 Int&& r0; // error C2530: 'r0' : references must be initialized 

rvalue- ++ , lvalue. مثال:


 Int i(7); Int&& r1 = i; // error C2440: 'initializing' : cannot convert from 'Int' to 'Int &&' 

rvalue:


 Int&& r2 = Int(42); // OK Int&& r3 = 5;       // OK 

lvalue rvalue-:


 Int&& r4 = static_cast<Int&&>(i); // OK 

rvalue- ( ) std::move() , ( <utility> ).


Rvalue rvalue , .


 int&& r5 = 2 * 2; // OK int& r6 = 2 * 2;  // error 

rvalue- .


 Int&& r = 7; std::cout << r.Get() << '\n'; // : 7 r.Set(19); std::cout << r.Get() << '\n'; // : 19 

Rvalue- .


 Int&& r = 5; Int& x = r;           // OK const Int& cx = r;    // OK 

Rvalue- , . , rvalue-, rvalue .


 void Foo(Int&&); Int i(7); Foo(i);            // error, lvalue  Foo(std::move(i)); // OK Foo(Int(4));       // OK Foo(5);            // OK 

, rvalue rvalue- , . rvalue-.


, , , rvalue-, (ambiguous) rvalue .



 void Foo(Int&&); void Foo(const Int&); 


 Int i(7); Foo(i);            // Foo(const Int&) Foo(std::move(i)); // Foo(Int&&) Foo(Int(6));       // Foo(Int&&) Foo(9);            // Foo(Int&&) 

: rvalue- lvalue.


 Int&& r = 7; Foo(r);            // Foo(const Int&) Foo(std::move(r)); // Foo(Int&&) 

, rvalue-, lvalue std::move() . . 2.4.


++11, rvalue- — -. (lvalue/rvalue) this .


 class X { public:    X();    void DoIt() &;  // this   lvalue    void DoIt() &&; // this   rvalue // ... }; X x; x.DoIt();   // DoIt() & X().DoIt(); // DoIt() && 


.


, ( std::string , std::vector<> , etc.) . — . , rvalue- . , , - , - , . , , , rvalue, lvalue. , rvalue. . , ( lvalue), RVO.



المراجع


[Alexandrescu]
, . C++.: . من اللغة الإنجليزية - م: LLC "I.D. », 2002.


[Guntheroth]
, . C++. .: . من اللغة الإنجليزية — .: «-», 2017.


[Josuttis]
, . C++: , 2- .: . من اللغة الإنجليزية - م: LLC "I.D. », 2014.


[Dewhurst1]
, . C++. , 2- .: . من اللغة الإنجليزية — .: -, 2013.


[Dewhurst2]
, . C++. .: . من اللغة الإنجليزية — .: , 2012.


[Meyers1]
, . C++. 35 .: . من اللغة الإنجليزية — .: , 2000.


[Meyers2]
, . C++. 55 .: . من اللغة الإنجليزية — .: , 2014.


[Meyers3]
, . C++: 42 C++11 C ++14.: . من اللغة الإنجليزية - م: LLC "I.D. », 2016.


[Sutter]
, . C++.: . من اللغة الإنجليزية — : «.. », 2015.


[Sutter/Alexandrescu]
, . , . ++.: . من اللغة الإنجليزية - م: LLC "I.D. », 2015.


[Schildt]
, . C++.: . من اللغة الإنجليزية — .: -, 2005.


[Alger]
, . C++: .: . من اللغة الإنجليزية — .: « «», 1999.




Source: https://habr.com/ru/post/ar425837/


All Articles