โยนคำหลักในลายเซ็นของฟังก์ชั่น


200

อะไรคือเหตุผลทางเทคนิคว่าทำไมจึงถือว่าการปฏิบัติที่ไม่ดีในการใช้throwคำหลักC ++ ในลายเซ็นฟังก์ชั่น?

bool some_func() throw(myExc)
{
  ...
  if (problem_occurred) 
  {
    throw myExc("problem occurred");
  }
  ...
}

ดูคำถามที่เกี่ยวข้องล่าสุดนี้: stackoverflow.com/questions/1037575/…
laalto


1
ไม่noexceptเปลี่ยนแปลงอะไร?
Aaron McDaid

12
ไม่มีอะไรผิดปกติกับการมีความคิดเห็นเกี่ยวกับบางสิ่งบางอย่างที่เกี่ยวข้องกับการเขียนโปรแกรม เกณฑ์การปิดนี้ไม่ดีอย่างน้อยสำหรับคำถามนี้ มันเป็นคำถามที่น่าสนใจพร้อมคำตอบที่น่าสนใจ
Anders Lindén

ควรสังเกตว่าข้อกำหนดคุณสมบัติข้อยกเว้นถูกเลิกใช้ตั้งแต่ C ++ 11
François Andrieux

คำตอบ:


127

ไม่มันไม่ถือว่าเป็นแนวปฏิบัติที่ดี ในทางตรงกันข้ามโดยทั่วไปถือว่าเป็นความคิดที่ไม่ดี

http://www.gotw.ca/publications/mill22.htmมีรายละเอียดมากขึ้นเกี่ยวกับสาเหตุ แต่ปัญหาคือบางส่วนที่คอมไพเลอร์ไม่สามารถบังคับใช้สิ่งนี้ได้ดังนั้นจึงต้องตรวจสอบที่รันไทม์ซึ่งโดยปกติจะเป็น ไม่พึงปรารถนา และไม่รองรับในทุกกรณี (MSVC ละเว้นข้อกำหนดข้อยกเว้นยกเว้น Throw () ซึ่งตีความว่าเป็นการรับประกันว่าจะไม่มีข้อยกเว้นเกิดขึ้น


25
ใช่. มีวิธีที่ดีกว่าในการเพิ่มช่องว่างในรหัสของคุณมากกว่าการโยน (myEx)
Assaf Lavie

4
ใช่คนที่เพิ่งค้นพบข้อกำหนดของข้อยกเว้นมักจะคิดว่าพวกเขาทำงานเหมือนใน Java ซึ่งคอมไพเลอร์สามารถบังคับใช้พวกเขาได้ ใน C ++ สิ่งนั้นจะไม่เกิดขึ้นซึ่งทำให้มีประโยชน์น้อยกว่ามาก
jalf

7
แต่ถ้าเป็นเช่นนั้นแล้ววัตถุประสงค์ด้านเอกสารจะเป็นเช่นไร? นอกจากนี้คุณยังจะได้รับการแจ้งว่าข้อยกเว้นใดที่คุณจะไม่มีวันจับแม้ว่าคุณจะลอง
Anders Lindén

1
@ AndersLindénวัตถุประสงค์เอกสารอะไร? หากคุณต้องการบันทึกการทำงานของรหัสของคุณเพียงแค่ใส่ความคิดเห็นไว้ด้านบน ไม่แน่ใจว่าคุณหมายถึงอะไรในส่วนที่สอง ข้อยกเว้นคุณจะไม่มีวันจับแม้ว่าคุณจะลอง
jalf

4
ฉันคิดว่ารหัสมีประสิทธิภาพเมื่อมันมาถึงเอกสารรหัสของคุณ (ความคิดเห็นสามารถโกหก) เอฟเฟกต์ "documentative" ที่ฉันอ้างถึงคือคุณจะรู้ได้อย่างแน่นอนว่าข้อยกเว้นใดที่คุณสามารถจับได้และสิ่งอื่น ๆ ไม่สามารถทำได้
Anders Lindén

57

Jalf เชื่อมโยงกับมันแล้ว แต่GOTWทำให้มันค่อนข้างเป็นอย่างดีว่าทำไมข้อมูลจำเพาะของข้อยกเว้นไม่เป็นประโยชน์อย่างที่ใคร ๆ ก็คาดหวัง:

int Gunc() throw();    // will throw nothing (?)
int Hunc() throw(A,B); // can only throw A or B (?)

ความคิดเห็นถูกต้องหรือไม่ ไม่มาก Gunc()อาจโยนบางสิ่งบางอย่างและHunc()อาจโยนสิ่งอื่นที่ไม่ใช่ A หรือ B! คอมไพเลอร์เพียงแค่รับประกันว่าจะเอาชนะพวกเขาอย่างไร้สติถ้าพวกเขาทำ…โอ้และเอาชนะโปรแกรมของคุณด้วยเช่นกันส่วนใหญ่แล้ว

นั่นเป็นเพียงสิ่งที่เกิดขึ้นคุณอาจจะจบลงด้วยการโทรหาterminate()และโปรแกรมของคุณกำลังจะตายอย่างรวดเร็ว แต่เจ็บปวด

ข้อสรุปของ GOTWs คือ:

ดังนั้นนี่คือสิ่งที่ดูเหมือนจะเป็นคำแนะนำที่ดีที่สุดที่เราในฐานะชุมชนได้เรียนรู้ ณ วันนี้:

  • คุณธรรม # 1:อย่าเขียนสเปคข้อยกเว้น
  • คุณธรรม # 2:ยกเว้นอาจจะว่างเปล่า แต่ถ้าฉันเป็นคุณฉันจะหลีกเลี่ยงได้

1
ฉันไม่แน่ใจว่าทำไมฉันถึงต้องมีข้อยกเว้นและไม่สามารถพูดถึงมันได้ แม้ว่ามันจะถูกโยนโดยฟังก์ชั่นอื่นฉันก็รู้ว่ามีข้อยกเว้นอะไรที่จะถูกโยน เหตุผลเดียวที่ฉันเห็นคือเพราะมันน่าเบื่อ
MasterMastic

3
@Ken: ประเด็นคือการเขียนข้อกำหนดข้อยกเว้นมีผลกระทบเชิงลบส่วนใหญ่ ผลเชิงบวกเพียงอย่างเดียวคือมันแสดงให้โปรแกรมเมอร์เห็นว่ามีข้อยกเว้นเกิดขึ้นได้อย่างไร แต่เนื่องจากมันไม่ได้ตรวจสอบโดยคอมไพเลอร์ด้วยวิธีที่สมเหตุสมผลจึงมีแนวโน้มที่จะเกิดข้อผิดพลาดและไม่คุ้มค่ามากนัก
sth

1
โอเคขอบคุณสำหรับการตอบสนอง ฉันเดาว่าเอกสารนี้มีไว้สำหรับ
MasterMastic

2
ไม่ถูกต้อง. ควรเขียนข้อกำหนดข้อยกเว้น แต่ความคิดคือการสื่อสารข้อผิดพลาดที่ผู้โทรควรพยายามจับ
HelloWorld

1
เช่นเดียวกับ @StudentT พูดว่า: มันเป็นความรับผิดชอบของฟังก์ชั่นในการรับประกันว่าจะไม่ส่งข้อยกเว้นอื่นใดเพิ่มเติม หากทำเช่นนั้นโปรแกรมจะยุติตามที่ควร ในการประกาศการโยนหมายความว่าฉันไม่รับผิดชอบที่จะจัดการกับสถานการณ์นี้และผู้โทรควรมีข้อมูลเพียงพอที่จะทำเช่นนี้ การไม่ประกาศข้อยกเว้นหมายความว่าพวกเขาสามารถเกิดขึ้นได้ทุกที่และสามารถจัดการได้ทุกที่ นั่นเป็นระเบียบต่อต้าน OOP มันเป็นความล้มเหลวในการออกแบบเพื่อตรวจจับข้อยกเว้นในสถานที่ที่ไม่ถูกต้อง ฉันขอแนะนำไม่ให้เว้นว่างเนื่องจากข้อยกเว้นพิเศษและฟังก์ชันส่วนใหญ่ควรเว้นว่างไว้
Jan Turoň

30

ในการเพิ่มมูลค่าให้กับคำตอบอื่น ๆ ของคำถามนี้คุณควรลงทุนในคำถามสองสามนาที: ผลลัพธ์ของโค้ดต่อไปนี้คืออะไร

#include <iostream>
void throw_exception() throw(const char *)
{
    throw 10;
}
void my_unexpected(){
    std::cout << "well - this was unexpected" << std::endl;
}
int main(int argc, char **argv){
    std::set_unexpected(my_unexpected);
    try{
        throw_exception();
    }catch(int x){
        std::cout << "catch int: " << x << std::endl;
    }catch(...){
        std::cout << "catch ..." << std::endl;
    }
}

คำตอบ: ตามที่ระบุไว้ที่นี่การเรียกใช้โปรแกรมstd::terminate()จึงไม่มีตัวจัดการข้อยกเว้นใด ๆ ที่จะถูกเรียก

รายละเอียด: my_unexpected()เรียกใช้ฟังก์ชันแรก แต่เนื่องจากไม่เรียกประเภทข้อยกเว้นที่ตรงกันสำหรับthrow_exception()ต้นแบบฟังก์ชันในตอนท้ายstd::terminate()จึงเรียกใช้ ดังนั้นเอาต์พุตเต็มจะมีลักษณะดังนี้:

ผู้ใช้ @ ผู้ใช้: ~ / tmp $ g ++ -o except.test ยกเว้น.test.cpp
ผู้ใช้ @: ~ / tmp $ ./except.test
ดี - นี่คือการ
ยกเลิกที่ไม่คาดคิดที่เรียกหลังจากโยนอินสแตนซ์ของ 'int'
ยกเลิก (หลัก ทิ้ง)


12

ผลกระทบเชิงปฏิบัติเพียงอย่างเดียวของตัวระบุการโยนคือหากสิ่งที่แตกต่างจากการmyExcโยนของคุณstd::unexpectedจะถูกเรียกใช้ (แทนที่จะเป็นกลไกการยกเว้นแบบปกติที่ไม่สามารถจัดการได้)

หากต้องการจัดทำเอกสารประเภทของข้อยกเว้นที่ฟังก์ชั่นสามารถโยนได้ฉันมักทำสิ่งนี้:

bool
some_func() /* throw (myExc) */ {
}

5
นอกจากนี้ยังมีประโยชน์ที่จะต้องทราบว่าการเรียกไปยัง std :: ที่ไม่คาดคิด () มักจะส่งผลให้มีการเรียกไปยัง std :: terminate () และจุดสิ้นสุดอย่างกระทันหันของโปรแกรมของคุณ
sth

1
- และ MSVC นั้นอย่างน้อยก็ไม่ได้ใช้พฤติกรรมนี้เท่าที่ฉันรู้
jalf

9

เมื่อเพิ่มรายละเอียดการขว้างปาลงในภาษามันเป็นความตั้งใจที่ดีที่สุด

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


9

ในขณะที่ googling เกี่ยวกับสเปคการโยนนี้ฉันได้ดูบทความนี้: - ( http://blogs.msdn.com/b/larryosterman/archive/2006/03/22/558390.aspx )

ฉันทำซ้ำส่วนหนึ่งของที่นี่ด้วยเพื่อที่จะสามารถใช้ในอนาคตโดยไม่คำนึงถึงความจริงที่ว่าลิงก์ด้านบนใช้งานได้หรือไม่

   class MyClass
   {
    size_t CalculateFoo()
    {
        :
        :
    };
    size_t MethodThatCannotThrow() throw()
    {
        return 100;
    };
    void ExampleMethod()
    {
        size_t foo, bar;
        try
        {
            foo = CalculateFoo();
            bar = foo * 100;
            MethodThatCannotThrow();
            printf("bar is %d", bar);
        }
        catch (...)
        {
        }
    }
};

เมื่อคอมไพเลอร์เห็นสิ่งนี้ด้วยแอ็ตทริบิวต์ "Throw ()" คอมไพเลอร์สามารถปรับค่าตัวแปร "bar" ให้สมบูรณ์เพราะมันรู้ว่าไม่มีทางยกเว้นสำหรับการโยนทิ้งจาก MethodThatCannotThrow () หากไม่มีแอ็ตทริบิวต์ Throw () คอมไพเลอร์จะต้องสร้างตัวแปร "bar" เพราะหาก MethodThatCannotThrow ส่งข้อยกเว้นตัวจัดการข้อยกเว้นอาจ / ขึ้นอยู่กับค่าของตัวแปร bar

นอกจากนี้เครื่องมือวิเคราะห์ซอร์สโค้ดเช่น prefast can (และจะ) ใช้คำอธิบายประกอบ throw () เพื่อปรับปรุงความสามารถในการตรวจจับข้อผิดพลาดของพวกเขา - ตัวอย่างเช่นหากคุณมี try / catch และฟังก์ชั่นทั้งหมดที่คุณโทรถูกทำเครื่องหมายว่าเป็น throw () คุณไม่จำเป็นต้องลอง / จับ (ใช่นี่มีปัญหาหากคุณเรียกใช้ฟังก์ชันที่สามารถโยนได้ในภายหลัง)


3

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

ด้วยคอมไพเลอร์บางตัวคุณสมบัติไม่ทิ้งในฟังก์ชั่นที่ไม่อินไลน์อาจเป็นประโยชน์หากการปรับให้เหมาะสมถูกต้องและการใช้ฟังก์ชั่นนั้นส่งผลต่อประสิทธิภาพในวิธีที่มันแสดงให้เห็นถึงความชอบธรรม

สำหรับฉันดูเหมือนว่าจะใช้หรือไม่เป็นการโทรที่สำคัญมากซึ่งเป็นส่วนหนึ่งของความพยายามในการเพิ่มประสิทธิภาพหรืออาจใช้เครื่องมือสร้างโปรไฟล์

อ้างจากลิงค์ด้านบนสำหรับผู้ที่รีบ (มีตัวอย่างของผลกระทบที่ไม่ตั้งใจไม่ดีจากการระบุการโยนในฟังก์ชั่นแบบอินไลน์จากคอมไพเลอร์ไร้เดียงสา):

เหตุผลข้อยกเว้นสเปค

ข้อมูลจำเพาะข้อยกเว้น [ISO 15.4] บางครั้งมีการเข้ารหัสเพื่อระบุว่ามีข้อยกเว้นใดบ้างที่จะถูกโยนทิ้งหรือเนื่องจากโปรแกรมเมอร์หวังว่าพวกเขาจะปรับปรุงประสิทธิภาพ แต่ให้พิจารณาสมาชิกต่อไปนี้จากตัวชี้สมาร์ท:

T & โอเปอเรเตอร์ * () const throw () {return * ptr; }

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

ตัวอย่างเช่นคอมไพเลอร์บางตัวปิดการอินไลน์ถ้ามีข้อยกเว้นสเปค คอมไพเลอร์บางตัวเพิ่มบล็อก try / catch การลบล้างดังกล่าวอาจเป็นความเสียหายด้านประสิทธิภาพซึ่งทำให้โค้ดไม่สามารถใช้งานได้จริงในแอปพลิเคชัน

แม้ว่าในขั้นต้นจะดึงดูดความสนใจ แต่ข้อยกเว้นสเปคมีแนวโน้มที่จะมีผลกระทบที่ต้องใช้ความคิดอย่างระมัดระวังที่จะเข้าใจ ปัญหาที่ใหญ่ที่สุดของข้อยกเว้นสเปคคือโปรแกรมเมอร์ใช้มันราวกับว่าพวกเขามีผลกระทบที่โปรแกรมเมอร์ต้องการแทนที่จะเป็นผลที่พวกเขามี

ฟังก์ชั่นที่ไม่ใช่แบบอินไลน์คือที่เดียวที่ "ข้อผิดพลาด" ไม่มีข้อผิดพลาด "อาจมีประโยชน์กับคอมไพเลอร์บางตัว


1
"ปัญหาที่ใหญ่ที่สุดของข้อกำหนดข้อยกเว้นคือโปรแกรมเมอร์ใช้มันราวกับว่าพวกเขามีผลกระทบที่โปรแกรมเมอร์ต้องการแทนที่จะเป็นผลที่พวกเขามีจริง ๆ " นี่คือเหตุผล # 1 ข้อผิดพลาดเมื่อสื่อสารกับเครื่องหรือผู้คน: ความแตกต่างระหว่างสิ่งที่เราพูดและสิ่งที่เราหมายถึง
Thagomizer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.