سؤال

يقول معظم الناس أبداً طرح استثناء من أداة التدمير - يؤدي القيام بذلك إلى سلوك غير محدد.يوضح ستروستروب هذه النقطة "يستدعي المدمر المتجه بشكل صريح المدمر لكل عنصر.وهذا يعني أنه في حالة إلقاء أداة تدمير العناصر، يفشل تدمير المتجهات...لا توجد حقًا طريقة جيدة للحماية من الاستثناءات التي يتم طرحها من أدوات التدمير، لذلك لا تقدم المكتبة أي ضمانات في حالة طرح أداة تدمير العناصر" (من الملحق E3.2).

هذا المقال يبدو أنه يقول خلاف ذلك - أن رمي المدمرات أمر جيد إلى حد ما.

لذا فإن سؤالي هو - إذا أدى الرمي من أداة التدمير إلى سلوك غير محدد، فكيف يمكنك التعامل مع الأخطاء التي تحدث أثناء أداة التدمير؟

في حالة حدوث خطأ أثناء عملية التنظيف، هل تتجاهله فحسب؟إذا كان هذا خطأ يمكن معالجته في المكدس ولكن ليس بشكل صحيح في أداة التدمير، أليس من المنطقي طرح استثناء من أداة التدمير؟

ومن الواضح أن هذه الأنواع من الأخطاء نادرة، ولكنها ممكنة.

هل كانت مفيدة؟

المحلول

يعد طرح استثناء من أداة التدمير أمرًا خطيرًا.
إذا تم نشر استثناء آخر بالفعل، فسيتم إنهاء التطبيق.

#include <iostream>

class Bad
{
    public:
        // Added the noexcept(false) so the code keeps its original meaning.
        // Post C++11 destructors are by default `noexcept(true)` and
        // this will (by default) call terminate if an exception is
        // escapes the destructor.
        //
        // But this example is designed to show that terminate is called
        // if two exceptions are propagating at the same time.
        ~Bad() noexcept(false)
        {
            throw 1;
        }
};
class Bad2
{
    public:
        ~Bad2()
        {
            throw 1;
        }
};


int main(int argc, char* argv[])
{
    try
    {
        Bad   bad;
    }
    catch(...)
    {
        std::cout << "Print This\n";
    }

    try
    {
        if (argc > 3)
        {
            Bad   bad; // This destructor will throw an exception that escapes (see above)
            throw 2;   // But having two exceptions propagating at the
                       // same time causes terminate to be called.
        }
        else
        {
            Bad2  bad; // The exception in this destructor will
                       // cause terminate to be called.
        }
    }
    catch(...)
    {
        std::cout << "Never print this\n";
    }

}

هذا يتلخص في الأساس في:

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

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

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

مثال:

الأمراض المنقولة جنسيا::fstream

من المحتمل أن تقوم طريقة الإغلاق () بطرح استثناء.يستدعي المدمر إغلاق () إذا تم فتح الملف ولكنه يتأكد من عدم انتشار أي استثناءات خارج المدمر.

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

لدى سكوت مايرز مقالة ممتازة حول هذا الموضوع في كتابه "Effective C++"

يحرر:

على ما يبدو أيضًا في "أكثر فعالية C++"
البند 11:منع الاستثناءات من ترك المدمرات

نصائح أخرى

يمكن أن يؤدي التخلص من أداة التدمير إلى حدوث عطل، لأنه قد يتم استدعاء أداة التدمير هذه كجزء من "تفكيك المكدس".تفكيك المكدس هو إجراء يحدث عند طرح استثناء.في هذا الإجراء، سيتم إنهاء جميع الكائنات التي تم دفعها إلى المكدس منذ "المحاولة" وحتى طرح الاستثناء -> سيتم استدعاء أدوات التدمير الخاصة بها.وأثناء هذا الإجراء، لا يُسمح برمي استثناء آخر، لأنه لا يمكن التعامل مع استثناءين في وقت واحد، وبالتالي، سيؤدي هذا إلى استدعاء abort()، وسيتعطل البرنامج وسيعود التحكم إلى نظام التشغيل.

علينا أن يميز هنا بدلاً من المتابعة العمياء عام نصيحة لل محدد حالات.

لاحظ أن ما يلي يتجاهل مسألة حاويات الأشياء وما يجب فعله في مواجهة تعدد محتويات الأشياء داخل الحاويات.(ويمكن تجاهل ذلك جزئيًا، نظرًا لأن بعض الكائنات ليست مناسبة لوضعها في حاوية.)

تصبح المشكلة برمتها أسهل في التفكير عندما نقوم بتقسيم الفصول الدراسية إلى نوعين.يمكن أن يكون لفئة dtor مسؤوليتين مختلفتين:

  • (R) إطلاق دلالات (ويعرف أيضًا باسم تحرير تلك الذاكرة)
  • (ج) يقترف دلالات (ويعرف أيضا باسم دافق ملف إلى القرص)

إذا نظرنا إلى السؤال بهذه الطريقة، فأعتقد أنه يمكن القول بأن دلالات (R) لا ينبغي أبدًا أن تسبب استثناءً من dtor حيث يوجد أ) لا شيء يمكننا القيام به حيال ذلك و ب) العديد من عمليات الموارد الحرة لا تفعل ذلك حتى توفير التحقق من الأخطاء، على سبيل المثال. void free(void* p);.

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

إذا اتبعنا مسار RAII وسمحنا للكائنات التي تحتوي على دلالات (C) في دورها، فأعتقد أنه يتعين علينا أيضًا السماح بالحالة الفردية حيث يمكن لمثل هذه الأدوار أن ترمي.ويترتب على ذلك أنه لا ينبغي عليك وضع مثل هذه الكائنات في حاويات ويترتب على ذلك أيضًا أن البرنامج لا يزال قادرًا على ذلك terminate() إذا تم طرح الالتزام dtor أثناء وجود استثناء آخر نشطًا.


وفيما يتعلق بمعالجة الأخطاء (دلالات الالتزام/التراجع) والاستثناءات، هناك حديث جيد من جانب واحد أندريه ألكسندريسكو: معالجة الأخطاء في C++ / تدفق التحكم التعريفي (عقد في مؤتمر الدفاع الوطني 2014)

وفي التفاصيل، يشرح كيف تقوم مكتبة Folly بتنفيذ برنامج UncaughtExceptionCounter لهم ScopeGuard الأدوات.

(يجب أن أشير إلى ذلك آحرون كان لديه أيضًا أفكار مماثلة.)

في حين أن الحديث لا يركز على الرمي من d'tor، فإنه يوضح الأداة التي يمكن استخدامها اليوم للتخلص من مشاكل مع متى رمي من دور.

في ال مستقبل, ، هناك يمكن تكون ميزة قياسية لهذا، يرى N3614, و أ مناقشة حول هذا الموضوع.

تحديث '17:ميزة C++ 17 std لهذا هي std::uncaught_exceptions afaikt.سأقتبس بسرعة مقالة cppref:

ملحوظات

مثال حيث int-عودة uncaught_exceptions يستخدم هو ......يقوم أولاً بإنشاء كائن حراسة ويسجل عدد الاستثناءات غير المطلوبة في مُنشئها.يتم تنفيذ الإخراج بواسطة تدمير كائن الحارس ما لم يلقي FOO () (في هذه الحالة ، يكون عدد الاستثناءات غير المطلوبة في المدمر أكبر مما لاحظه المنشئ)

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

إذا قمت بتدمير أ Foo الكائن، و Foo يطرح المدمر استثناءً، ما الذي يمكنني فعله بشكل معقول به؟يمكنني تسجيل ذلك، أو يمكنني تجاهله.هذا كل شئ.لا أستطيع "إصلاحه"، لأن Foo لقد ذهب الكائن بالفعل.في أفضل الأحوال، أقوم بتسجيل الاستثناء وأستمر كما لو لم يحدث شيء (أو أنهي البرنامج).هل يستحق هذا حقًا التسبب في سلوك غير محدد عن طريق الرمي من المدمر؟

إنه أمر خطير، ولكنه أيضًا غير منطقي من وجهة نظر سهولة القراءة/فهم الكود.

ما عليك أن تسأله هو في هذه الحالة

int foo()
{
   Object o;
   // As foo exits, o's destructor is called
}

ما الذي يجب أن يمسك بالاستثناء؟وينبغي للمتصل فو؟أو ينبغي فو التعامل معها؟لماذا يجب أن يهتم المتصل بـ foo ببعض الكائنات الداخلية لـ foo؟قد تكون هناك طريقة تحدد بها اللغة هذا الأمر لجعله منطقيًا، لكنها ستكون غير قابلة للقراءة ويصعب فهمها.

والأهم من ذلك، أين تذهب ذاكرة الكائن؟أين تذهب ذاكرة الكائن المملوك؟هل لا يزال مخصصًا (ظاهريًا لأن المدمر فشل)؟ضع في اعتبارك أيضًا أن الكائن كان موجودًا مساحة المكدس, ، لذلك من الواضح أنه ذهب بغض النظر.

ثم النظر في هذه الحالة

class Object
{ 
   Object2 obj2;
   Object3* obj3;
   virtual ~Object()
   {
       // What should happen when this fails? How would I actually destroy this?
       delete obj3;

       // obj 2 fails to destruct when it goes out of scope, now what!?!?
       // should the exception propogate? 
   } 
};

عندما يفشل حذف obj3، كيف يمكنني حذفه فعليًا بطريقة مضمونة عدم الفشل؟انها ذاكرتي اللعنة!

الآن ضع في اعتبارك في مقتطف التعليمات البرمجية الأول أن الكائن يختفي تلقائيًا لأنه موجود في المكدس بينما يكون Object3 في الكومة.منذ اختفاء المؤشر إلى Object3، فأنت نوع من SOL.لديك تسرب للذاكرة.

الآن إحدى الطرق الآمنة للقيام بالأشياء هي ما يلي

class Socket
{
    virtual ~Socket()
    {
      try 
      {
           Close();
      }
      catch (...) 
      {
          // Why did close fail? make sure it *really* does close here
      }
    } 

};

انظر هذا أيضا التعليمات

من مسودة ISO لـ C++ (ISO/IEC JTC 1/SC 22 N 4411)

لذلك يجب على المدمرات عمومًا التقاط الاستثناءات وعدم السماح لها بالانتشار خارج المدمر.

3 تسمى عملية استدعاء المدمرات للكائنات التلقائية التي تم إنشاؤها على المسار من كتلة المحاولة إلى تعبير رمي "استرخاء المكدس". [ ملحوظة:إذا قام أحد المدمرات التي تسمى أثناء خروج رفقة المكدس باستثناء ، يتم استدعاء std :: إنهاء (15.5.1).لذلك يجب على المدمرين أن يلتقطوا استثناءات عمومًا ولا يسمحون لهم بالانتشار من المدمر.— ملاحظة النهاية ]

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

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

على سبيل المثال:

class TempFile {
public:
    TempFile(); // throws if the file couldn't be created
    ~TempFile() throw(); // does nothing if close() was already called; never throws
    void close(); // throws if the file couldn't be deleted (e.g. file is open by another process)
    // the rest of the class omitted...
};

بالإضافة إلى الإجابات الرئيسية الجيدة والشاملة والدقيقة، أود التعليق على المقالة التي تشير إليها - تلك التي تقول "رمي الاستثناءات في المدمرات ليس سيئًا للغاية".

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

المشكلة هي أن أيًا من المشكلات التي يدرجها مع البدائل لا يقترب من سوء سلوك الاستثناء، والذي، دعونا نتذكر، هو "سلوك غير محدد لبرنامجك".تشمل بعض اعتراضات المؤلف "القبيح من الناحية الجمالية" و"التشجيع على الأسلوب السيئ".الآن ما الذي تفضله؟برنامج ذو أسلوب سيء، أو برنامج أظهر سلوكًا غير محدد؟

أنا في المجموعة التي ترى أن نمط "الحارس المحدد النطاق" الذي يتم رميه في المدمر مفيد في العديد من المواقف - خاصة بالنسبة لاختبارات الوحدة.ومع ذلك، انتبه إلى أنه في الإصدار C++ 11، يؤدي استخدام أداة التدمير إلى استدعاء std::terminate نظرًا لأن المدمرات يتم شرحها ضمنيًا بـ noexcept.

لدى Andrzej Krzemieński منشور رائع حول موضوع المدمرات التي ترمي:

ويشير إلى أن C++ 11 لديه آلية لتجاوز الإعداد الافتراضي noexcept للمدمرين:

في C++ 11، يتم تحديد المدمر ضمنيًا على أنه noexcept.حتى لو لم تقم بإضافة أي مواصفات وتعريف المدمر الخاص بك مثل هذا:

  class MyType {
        public: ~MyType() { throw Exception(); }            // ...
  };

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

  • حدد المدمر الخاص بك بشكل صريح كـ noexcept(false),
  • ترث صفك من صف آخر يحدد بالفعل المدمر الخاص به كـ noexcept(false).
  • ضع عضو بيانات غير ثابت في الفصل الدراسي الخاص بك والذي يحدد بالفعل أداة التدمير الخاصة به noexcept(false).

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

س:لذا فإن سؤالي هو - إذا أدى الرمي من المدمرة إلى سلوك غير محدد ، فكيف تتعامل مع الأخطاء التي تحدث أثناء المدمار؟

أ:هناك عدة خيارات:

  1. دع الاستثناءات تتدفق من المدمر الخاص بك، بغض النظر عما يحدث في مكان آخر.وعند القيام بذلك كن على دراية (أو حتى خائفًا) من أن std::terminate قد يتبع ذلك.

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

  3. المفضلة لدي :لو std::uncaught_exception إرجاع كاذبة، تتيح لك تدفق الاستثناءات.إذا عاد صحيحا، فارجع إلى نهج التسجيل.

ولكن هل من الجيد رمي d'tors؟

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

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

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

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

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

مارتن با (أعلاه) يسير على الطريق الصحيح - أنت تصمم بشكل مختلف من أجل منطق الإصدار والالتزام.

من أجل إطلاق:

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

للالتزام:

هذا هو المكان الذي تريد فيه نفس النوع من كائنات غلاف RAII التي توفرها أشياء مثل std::lock_guard لكائنات المزامنة.مع هؤلاء لا تضع منطق الالتزام في dtor على الإطلاق.لديك واجهة برمجة تطبيقات مخصصة لها، ثم كائنات مجمّعة ستلتزم بها RAII في dtors وتتعامل مع الأخطاء هناك.تذكر أنه يمكنك التقاط الاستثناءات في أداة التدمير بشكل جيد؛إصدارها وهذا أمر مميت.يتيح لك هذا أيضًا تنفيذ السياسة ومعالجة الأخطاء المختلفة فقط عن طريق إنشاء غلاف مختلف (على سبيل المثال.الأمراض المنقولة جنسيا::unique_lock مقابل.std::lock_guard)، ويضمن أنك لن تنسى استدعاء منطق الالتزام - وهو المبرر الوحيد اللائق في منتصف الطريق لوضعه في dtor في المقام الأول.

ضبط حدث التنبيه.عادةً ما تكون أحداث الإنذار نموذجًا أفضل للإخطار بالفشل أثناء تنظيف الكائنات

على عكس المنشئات، حيث يمكن أن يكون طرح الاستثناءات طريقة مفيدة للإشارة إلى نجاح إنشاء الكائن، لا ينبغي طرح الاستثناءات في أدوات التدمير.

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

وبالتالي، فإن أفضل مسار للعمل هو الامتناع تمامًا عن استخدام الاستثناءات في أدوات التدمير.اكتب رسالة إلى ملف سجل بدلاً من ذلك.

لذا فإن سؤالي هو - إذا أدى الرمي من المدمرة إلى سلوك غير محدد ، فكيف تتعامل مع الأخطاء التي تحدث أثناء المدمار؟

المشكلة الرئيسية هي هذه:لا يمكنك تفشل في الفشل.ماذا يعني الفشل في الفشل، بعد كل شيء؟إذا فشل تنفيذ معاملة إلى قاعدة بيانات، وفشلت (فشلت في التراجع)، فماذا يحدث لسلامة بياناتنا؟

نظرًا لأنه يتم استدعاء المدمرات لكل من المسارات العادية والاستثنائية (الفاشلة)، فهي نفسها لا يمكن أن تفشل وإلا فإننا "نفشل في الفشل".

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

ربما يكون الحل العملي هو التأكد من أن فرص الفشل في الفشل غير محتملة من الناحية الفلكية ، لأن جعل الأمور مستحيلة الفشل قد يكون مستحيلًا تقريبًا في بعض الحالات.

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

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

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

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

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

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

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top