سؤال

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

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

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

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

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

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

المحلول

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

يمكن القيام بذلك عن طريق وضع البيانات المراد حفظها على الكومة والتنفيذ swap (move في C ++ 0x) على بياناتك الخاصة داخل الاستثناء ، على سبيل المثال.

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

struct exception_data { // abstract base class; may contain anything
    virtual ~exception_data() {}
};

struct chained_exception : std::exception {
    chained_exception( std::string const &s, exception_data *d = NULL )
        : data(d), descr(s) {
        try {
            link = new chained_exception;
            throw;
        } catch ( chained_exception &prev ) {
            swap( *link, prev );
        } // catch std::bad_alloc somehow...
    }

    friend void swap( chained_exception &lhs, chained_exception &rhs ) {
        std::swap( lhs.link, rhs.link );
        std::swap( lhs.data, rhs.data );
        swap( lhs.descr, rhs.descr );
    }

    virtual char const *what() const throw() { return descr.c_str(); }

    virtual ~chained_exception() throw() {
        if ( link && link->link ) delete link; // do not delete terminator
        delete data;
    }

    chained_exception *link; // always on heap
    exception_data *data; // always on heap
    std::string descr; // keeps data on heap

private:
    chained_exception() : link(), data() {}
    friend int main();
};

void f() {
    try {
        throw chained_exception( "humbug!" );
    } catch ( std::exception & ) {
        try {
            throw chained_exception( "bah" );
        } catch ( chained_exception &e ) {
            chained_exception *ep = &e;
            for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
                std::cerr << ep->what() << std::endl;
            }
        }
    }

    try {
        throw chained_exception( "meh!" );
    } catch ( chained_exception &e ) {
        for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
            std::cerr << ep->what() << std::endl;
        }
    }
}

int main() try {
    throw chained_exception(); // create dummy end-of-chain
} catch( chained_exception & ) {
    // body of main goes here
    f();
}

الإخراج (غاضب بشكل مناسب):

bah
humbug!
meh!

نصائح أخرى

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

يستخدم std::nested_exception و std::throw_with_nested

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

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

Library API: Exception caught in function 'api_function'
Backtrace:
~/Git/mwe-cpp-exception/src/detail/Library.cpp:17 : library_function failed
~/Git/mwe-cpp-exception/src/detail/Library.cpp:13 : could not open file "nonexistent.txt"

قد ترغب في إلقاء نظرة على هذا: http://www.boost.org/doc/libs/1_43_0/libs/exception/doc/boost-exception.html

إنه نهج مختلف إلى حد ما لما فعله MS في C#، ولكن يبدو أنه يتطابق مع متطلباتك.

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

أراهن بما إذا كانت أي معلومات مكدس متاحة على الإطلاق محددة للتنفيذ ، فإن أن التطبيقات ستختلف بشكل أكبر في ما إذا كانت قد تم الحفاظ عليها بأي طريقة بعد عارية أم لا throw; بيان.

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