استثناء ج++:رمي الأمراض المنقولة جنسيا::سلسلة

StackOverflow https://stackoverflow.com/questions/134569

  •  02-07-2019
  •  | 
  •  

سؤال

أرغب في طرح استثناء عندما تواجه أساليب C++ الخاصة بي شيئًا غريبًا ولا يمكن استردادها.هل من المقبول رمي أ std::string المؤشر؟

إليك ما كنت أتطلع إلى القيام به:

void Foo::Bar() {
    if(!QueryPerformanceTimer(&m_baz)) {
        throw new std::string("it's the end of the world!");
    }
}

void Foo::Caller() {
    try {
        this->Bar(); // should throw
    }
    catch(std::string *caught) { // not quite sure the syntax is OK here...
        std::cout << "Got " << caught << std::endl;
    }
}
هل كانت مفيدة؟

المحلول

نعم. std::exception هي فئة الاستثناء الأساسية في مكتبة C++ القياسية.قد ترغب في تجنب استخدام السلاسل كفئات استثناء لأنها يمكنها أن تطرح استثناءً أثناء الاستخدام.إذا حدث ذلك، فأين ستكون؟

دفعة لديه ممتازة وثيقة على الأسلوب الجيد للاستثناءات ومعالجة الأخطاء.انه يستحق القراءة.

نصائح أخرى

بعض المبادئ:

  1. لديك فئة أساسية std::exception، ويجب أن تشتق منها الاستثناءات.بهذه الطريقة لا يزال لدى معالج الاستثناء العام بعض المعلومات.

  2. لا ترمي المؤشرات بل اعترض، وبهذه الطريقة يتم التعامل مع الذاكرة نيابةً عنك.

مثال:

struct MyException : public std::exception
{
   std::string s;
   MyException(std::string ss) : s(ss) {}
   ~MyException() throw () {} // Updated
   const char* what() const throw() { return s.c_str(); }
};

ثم استخدمه في الكود الخاص بك:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw MyException("it's the end of the world!");
  }
}

void Foo::Caller(){
  try{
    this->Bar();// should throw
  }catch(MyException& caught){
    std::cout<<"Got "<<caught.what()<<std::endl;
  }
}

كل هذه الأعمال:

#include <iostream>
using namespace std;

//Good, because manual memory management isn't needed and this uses
//less heap memory (or no heap memory) so this is safer if
//used in a low memory situation
void f() { throw string("foo"); }

//Valid, but avoid manual memory management if there's no reason to use it
void g() { throw new string("foo"); }

//Best.  Just a pointer to a string literal, so no allocation is needed,
//saving on cleanup, and removing a chance for an allocation to fail.
void h() { throw "foo"; }

int main() {
  try { f(); } catch (string s) { cout << s << endl; }
  try { g(); } catch (string* s) { cout << *s << endl; delete s; }
  try { h(); } catch (const char* s) { cout << s << endl; }
  return 0;
}

يجب أن تفضل h إلى f إلى g.لاحظ أنه في الخيار الأقل تفضيلاً تحتاج إلى تحرير الذاكرة بشكل صريح.

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

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

بالإضافة إلى احتمال رمي شيء مشتق من std::exception، يجب عليك رمي مؤقتات مجهولة والتقاطها حسب المرجع:

void Foo::Bar(){
  if(!QueryPerformanceTimer(&m_baz)){
    throw std::string("it's the end of the world!");
  }
}

void Foo:Caller(){
  try{
    this->Bar();// should throw
  }catch(std::string& caught){ // not quite sure the syntax is ok here...
    std::cout<<"Got "<<caught<<std::endl;
  }
}
  • يجب أن ترمي المنشورات المجهولة حتى يتعامل المترجم مع الكائن مدى الحياة لأي شيء ترميه - إذا رميت شيئًا جديدًا عن الكومة ، يحتاج شخص آخر إلى تحرير الشيء.
  • يجب عليك التقاط مراجع لمنع تقطيع الكائنات

.

راجع "Effective C++ - الإصدار الثالث" من Meyer للحصول على التفاصيل أو قم بزيارة الموقع https://www.securecoding.cert.org/.../ERR02-A.+Throw+anonymous+temporaries+and+catch+by+reference

أبسط طريقة لرمي استثناء في C++:

#include <iostream>
using namespace std;
void purturb(){
    throw "Cannot purturb at this time.";
}
int main() {
    try{
        purturb();
    }
    catch(const char* msg){
        cout << "We caught a message: " << msg << endl;
    }
    cout << "done";
    return 0;
}

هذا يطبع:

We caught a message: Cannot purturb at this time.
done

إذا صادفت الاستثناء الذي تم طرحه، فسيتم احتواء الاستثناء وسيستمر البرنامج.إذا لم تكتشف الاستثناء، فسيكون البرنامج موجودًا ويطبع:

This application has requested the Runtime to terminate it in an unusual way. Please contact the application's support team for more information.

على الرغم من أن هذا السؤال قديم إلى حد ما وقد تمت الإجابة عليه بالفعل، إلا أنني أريد فقط إضافة ملاحظة حول كيفية القيام بالمعالجة المناسبة للاستثناءات في سي++ 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"
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top