วิธีการโยนข้อยกเว้น C ++


259

ฉันมีความเข้าใจที่แย่มากเกี่ยวกับการจัดการข้อยกเว้น (เช่นวิธีการปรับแต่งการโยนลองจับคำแถลงเพื่อจุดประสงค์ของฉันเอง)

ตัวอย่างเช่นฉันได้กำหนดฟังก์ชันดังนี้: int compare(int a, int b){...}

ฉันต้องการให้ฟังก์ชันส่งข้อยกเว้นพร้อมข้อความบางข้อความเมื่อ a หรือ b เป็นลบ

ฉันจะเข้าหาสิ่งนี้ในนิยามของฟังก์ชันได้อย่างไร?


3
คุณควรอ่านบทความนี้: gotw.ca/publications/mill22.htm
Oliver Charlesworth

37
@OliCharlesworth คุณไม่คิดว่ามันจะเป็นเรื่องเล็ก ๆ น้อย ๆ ที่จะทำให้คนที่สับสนกับพื้นฐาน?
Mark Ransom

6
ข้อยกเว้นที่เกินความจำเป็นควรหลีกเลี่ยง หากคุณไม่ต้องการให้ผู้โทรส่งค่าลบให้ชัดเจนยิ่งขึ้นโดยระบุunsigned intเป็นพารามิเตอร์ในฟังก์ชันลายเซ็นของคุณ จากนั้นอีกครั้งฉันอยู่ในโรงเรียนที่คุณควรโยนและจับข้อยกเว้นสำหรับสิ่งที่พิเศษจริงๆ
AJG85

1
@ Mark: ตอนแรกฉันเข้าใจผิดคำถามว่าควรใช้throw()ข้อมูลจำเพาะข้อยกเว้นสำหรับฟังก์ชั่นหรือไม่
Oliver Charlesworth

คำตอบ:


363

ง่าย:

#include <stdexcept>

int compare( int a, int b ) {
    if ( a < 0 || b < 0 ) {
        throw std::invalid_argument( "received negative value" );
    }
}

ไลบรารีมาตรฐานมาพร้อมกับคอลเลกชันที่ดีของวัตถุในตัวที่คุณสามารถโยนได้ โปรดจำไว้ว่าคุณควรโยนด้วยมูลค่าและอ้างอิงโดยอ้างอิงเสมอ:

try {
    compare( -1, 3 );
}
catch( const std::invalid_argument& e ) {
    // do stuff with exception... 
}

คุณสามารถมีคำสั่ง catch () หลายรายการหลังจากลองแต่ละครั้งเพื่อให้คุณสามารถจัดการประเภทข้อยกเว้นต่าง ๆ แยกกันได้หากคุณต้องการ

คุณยังสามารถโยนข้อยกเว้นใหม่ได้:

catch( const std::invalid_argument& e ) {
    // do something

    // let someone higher up the call stack handle it if they want
    throw;
}

และจับข้อยกเว้นโดยไม่คำนึงถึงประเภท:

catch( ... ) { };

26
และคุณควรจะได้รับการยกเว้นเป็น const
Adrian Cornish

2
@TerryLiYifeng ถ้าข้อยกเว้นที่กำหนดเองทำให้รู้สึกมากขึ้นแล้วไปทำมัน คุณอาจยังต้องการสืบเนื่องมาจาก std :: exception และทำให้อินเทอร์เฟซเหมือนเดิม
nsanders

2
+1 อีกครั้ง แต่ฉันคิดว่า const มันสำคัญมาก - เพราะมันเน้นความจริงที่ว่ามันเป็นวัตถุชั่วคราวในขณะนี้ - ดังนั้นการปรับเปลี่ยนจึงไม่มีประโยชน์
Adrian Cornish

2
@AdrianCornish: มันไม่ได้ชั่วคราวจริงๆ จับ Non-const จะมีประโยชน์
GManNickG

26
โดยปกติแล้วคุณจะต้องสร้างวัตถุที่เรียบง่ายขึ้นมาใหม่throw;(ทำการรื้อวัตถุเดิมและรักษาชนิดของมัน) แทนที่จะทำซ้ำthrow e;(โยนสำเนาของวัตถุที่ถูกจับได้
Mike Seymour

17

เพียงเพิ่มthrowที่จำเป็นและtryปิดกั้นผู้โทรที่จัดการข้อผิดพลาด โดยการประชุมคุณควรโยนสิ่งที่ได้รับจากstd::exceptionดังนั้นรวมถึง<stdexcept>ก่อน

int compare(int a, int b) {
    if (a < 0 || b < 0) {
        throw std::invalid_argument("a or b negative");
    }
}

void foo() {
    try {
        compare(-1, 0);
    } catch (const std::invalid_argument& e) {
        // ...
    }
}

ดูที่Boost.Exceptionด้วย


15

แม้ว่าคำถามนี้ค่อนข้างเก่าและได้รับคำตอบแล้ว แต่ฉันต้องการเพิ่มบันทึกเกี่ยวกับวิธีการจัดการข้อยกเว้นที่เหมาะสมใน C ++ 11:

ใช้std::nested_exceptionและstd::throw_with_nested

มันอธิบายไว้ใน StackOverflow ที่นี่และที่นี่วิธีที่คุณสามารถรับ backtrace ในข้อยกเว้นของคุณของคุณในรหัสของคุณโดยไม่จำเป็นต้องมีการดีบักหรือการบันทึกยุ่งยากโดยเพียงแค่เขียนตัวจัดการข้อยกเว้นที่เหมาะสมซึ่งจะ rethrow ข้อยกเว้นซ้อน

เนื่องจากคุณสามารถทำได้ด้วยคลาสยกเว้นที่ได้รับคุณสามารถเพิ่มข้อมูลจำนวนมากลงใน backtrace! คุณอาจจะดูMWEของฉันบน GitHubด้วยที่ backtrace จะมีหน้าตาแบบนี้:

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"

8

คุณสามารถกำหนดข้อความที่จะโยนเมื่อเกิดข้อผิดพลาดบางอย่าง:

throw std::invalid_argument( "received negative value" );

หรือคุณสามารถกำหนดเช่นนี้

std::runtime_error greatScott("Great Scott!");          
double getEnergySync(int year) {                        
    if (year == 1955 || year == 1885) throw greatScott; 
    return 1.21e9;                                      
}                                                       

โดยทั่วไปคุณจะมีtry ... catchบล็อกดังนี้:

try {
// do something that causes an exception
}catch (std::exception& e){ std::cerr << "exception: " << e.what() << std::endl; }

6

อยากจะเพิ่มคำตอบอื่น ๆ ที่อธิบายที่นี่ทราบเพิ่มเติมในกรณีของข้อยกเว้นที่กำหนดเอง

ในกรณีที่คุณสร้างข้อยกเว้นที่กำหนดเองของคุณเองซึ่งเกิดขึ้นจากstd::exceptionเมื่อคุณตรวจสอบประเภทข้อยกเว้น "ที่เป็นไปได้ทั้งหมด" คุณควรเริ่มต้นส่วนcatchคำสั่งด้วยประเภทข้อยกเว้น ดูตัวอย่าง (สิ่งที่ไม่ต้องทำ):

#include <iostream>
#include <string>

using namespace std;

class MyException : public exception
{
public:
    MyException(const string& msg) : m_msg(msg)
    {
        cout << "MyException::MyException - set m_msg to:" << m_msg << endl;
    }

   ~MyException()
   {
        cout << "MyException::~MyException" << endl;
   }

   virtual const char* what() const throw () 
   {
        cout << "MyException - what" << endl;
        return m_msg.c_str();
   }

   const string m_msg;
};

void throwDerivedException()
{
    cout << "throwDerivedException - thrown a derived exception" << endl;
    string execptionMessage("MyException thrown");
    throw (MyException(execptionMessage));
}

void illustrateDerivedExceptionCatch()
{
    cout << "illustrateDerivedExceptionsCatch - start" << endl;
    try 
    {
        throwDerivedException();
    }
    catch (const exception& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an std::exception, e.what:" << e.what() << endl;
        // some additional code due to the fact that std::exception was thrown...
    }
    catch(const MyException& e)
    {
        cout << "illustrateDerivedExceptionsCatch - caught an MyException, e.what::" << e.what() << endl;
        // some additional code due to the fact that MyException was thrown...
    }

    cout << "illustrateDerivedExceptionsCatch - end" << endl;
}

int main(int argc, char** argv)
{
    cout << "main - start" << endl;
    illustrateDerivedExceptionCatch();
    cout << "main - end" << endl;
    return 0;
}

บันทึก:

0) ลำดับที่ถูกต้องควรเป็นในทางกลับกันคือ - อันดับแรกคุณcatch (const MyException& e)ตามมาด้วยcatch (const std::exception& e)ซึ่งตามมาด้วย

1) อย่างที่คุณเห็นเมื่อคุณรันโปรแกรมตามที่เป็นอยู่ประโยคแรกที่จับจะถูกดำเนินการ (ซึ่งอาจเป็นสิ่งที่คุณไม่ต้องการในตอนแรก)

2) แม้ว่าประเภทที่จับในประโยคแรกที่จับได้เป็นประเภทstd::exceptionรุ่น "เหมาะสม" ของwhat()จะถูกเรียก - เพราะมันถูกจับได้โดยอ้างอิง (เปลี่ยนอย่างน้อยstd::exceptionประเภทอาร์กิวเมนต์ที่ถูกจับจะเป็นค่า - และคุณจะได้สัมผัส ปรากฏการณ์ "การแบ่งวัตถุ" ในการทำงาน)

3) ในกรณีที่ "บางรหัสเนื่องจากความจริงที่ว่ามีข้อยกเว้น XXX ถูกโยน ... " ทำสิ่งที่สำคัญด้วยความเคารพต่อประเภทข้อยกเว้นมีการประพฤติผิดของรหัสของคุณที่นี่

4) สิ่งนี้ก็มีความเกี่ยวข้องเช่นกันหากวัตถุที่จับได้นั้นเป็นวัตถุ "ปกติ" เช่น: class Base{};และclass Derived : public Base {} ...

5) g++ 7.3.0บน Ubuntu 18.04.1 สร้างคำเตือนที่บ่งบอกถึงปัญหาที่กล่าวถึง:

ในฟังก์ชั่น 'void illustrateDerivedExceptionCatch ()': item12Linux.cpp: 48: 2: คำเตือน: ข้อยกเว้นประเภท 'MyException'จะถูกตรวจจับ (const MyException & e) ^ ~~~~

item12Linux.cpp: 43: 2: คำเตือน: โดยตัวจัดการก่อนหน้านี้สำหรับการจับ 'std :: exception' (ข้อยกเว้น const & e) ^ ~~~~

อีกครั้งฉันจะบอกว่าคำตอบนี้เป็นเพียงการเพิ่มไปยังคำตอบอื่น ๆ ที่อธิบายไว้ที่นี่ (ฉันคิดว่าประเด็นนี้มีมูลค่าการกล่าวถึง แต่ยังไม่สามารถอธิบายได้ภายในความคิดเห็น)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.