เมื่อใดที่ฉันควรใช้ noexcept จริงๆ


507

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

  1. มีตัวอย่างมากมายของฟังก์ชั่นที่ฉันรู้ว่าจะไม่ทิ้ง แต่สำหรับคอมไพเลอร์ไม่สามารถกำหนดได้ด้วยตัวเอง ฉันควรผนวกท้ายnoexceptประกาศฟังก์ชันในทุกกรณีเช่นนี้หรือไม่?

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

  2. ฉันสามารถเมื่อแนบเนียนคาดหวังที่จะสังเกตการปรับปรุงประสิทธิภาพการทำงานหลังจากที่ใช้noexcept? โดยเฉพาะอย่างยิ่งให้ตัวอย่างของรหัสที่คอมไพเลอร์ C ++ noexceptสามารถสร้างรหัสเครื่องดีขึ้นหลังจากที่นอกเหนือจากคะแนน

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


40
รหัสที่ใช้move_if_nothrow(หรือ whatchamacallit) จะเห็นการปรับปรุงประสิทธิภาพหากไม่มี ctor ย้ายแบบไม่ยกเว้น
R. Martinho Fernandes

ที่เกี่ยวข้อง: github.com/isocpp/CppCoreGuidelines/blob/master/…
moooeeeep

5
move_if_noexceptมัน
Nikos

คำตอบ:


180

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

ต้องคิดก่อนว่าจะต้องต่อท้ายหรือไม่noexceptหลังจากการประกาศฟังก์ชั่นทุกครั้งจะช่วยลดความสามารถในการผลิตของโปรแกรมเมอร์ได้อย่างมาก

จากนั้นใช้เมื่อเห็นได้ชัดว่าฟังก์ชั่นจะไม่ทิ้ง

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

ดูเหมือนว่าการเพิ่มประสิทธิภาพที่ใหญ่ที่สุดนั้นมาจากการปรับปรุงผู้ใช้ไม่ใช่คอมไพเลอร์เนื่องจากความเป็นไปได้ในการตรวจสอบnoexceptและการโอเวอร์โหลด คอมไพเลอร์ส่วนใหญ่ใช้วิธีการจัดการข้อยกเว้นแบบไม่มีโทษ - ถ้าคุณไม่โยนดังนั้นฉันสงสัยว่ามันจะเปลี่ยนแปลงมาก (หรืออะไรก็ได้) ในระดับรหัสเครื่องของรหัสของคุณแม้ว่าอาจลดขนาดไบนารีโดยการลบ รหัสการจัดการ

การใช้งานnoexceptในสี่ตัวใหญ่ (ตัวสร้างการมอบหมายไม่ใช่ตัวทำลายสิ่งที่เป็นอยู่แล้วnoexcept) จะทำให้เกิดการปรับปรุงที่ดีที่สุดเนื่องจากการnoexceptตรวจสอบเป็น 'สามัญ' ในรหัสแม่แบบเช่นในstdภาชนะบรรจุ ตัวอย่างเช่นstd::vectorจะไม่ใช้การย้ายชั้นเรียนของคุณยกเว้นว่ามีการทำเครื่องหมายไว้noexcept(หรือคอมไพเลอร์สามารถอนุมานได้)


6
ฉันคิดว่าstd::terminateเคล็ดลับยังคงเชื่อฟังแบบจำลอง Zero-Cost นั่นคือมันเกิดขึ้นเพียงว่าช่วงของคำสั่งภายในnoexceptฟังก์ชั่นถูกแมปเพื่อโทรstd::terminateถ้าthrowใช้แทนสแต็กคลี่คลาย ฉันจึงสงสัยว่ามีค่าใช้จ่ายมากกว่าปกติในการติดตามข้อยกเว้น
Matthieu M.

4
@Klaim ดูสิ่งนี้: stackoverflow.com/a/10128180/964135จริงๆแล้วมันไม่ได้มีการขว้าง แต่noexceptรับประกันได้
Pubby

3
"ถ้าnoexceptฟังก์ชั่นการโยนstd::terminateถูกเรียกแล้วซึ่งดูเหมือนว่ามันจะเกี่ยวข้องกับค่าใช้จ่ายเล็กน้อย" ... ไม่ควรดำเนินการนี้โดยไม่สร้างตารางข้อยกเว้นสำหรับฟังก์ชั่นดังกล่าวซึ่งตัวแจกจ่ายข้อยกเว้นควรจับแล้วประกันตัวออก
Potatoswatter

8
การจัดการข้อยกเว้น @Pubby C ++ มักจะทำโดยไม่มีค่าใช้จ่ายยกเว้นตารางข้ามซึ่งแมปอาจโยนที่อยู่ของไซต์โทรไปยังจุดเริ่มต้นของตัวจัดการ การลบตารางเหล่านั้นใกล้เคียงที่สุดเท่าที่จะทำได้เพื่อลบการจัดการข้อยกเว้นออกอย่างสมบูรณ์ ความแตกต่างเพียงอย่างเดียวคือขนาดไฟล์ที่เรียกใช้งานได้ อาจจะไม่คุ้มค่าที่จะพูดถึงอะไร
Potatoswatter

26
"จากนั้นใช้เมื่อเห็นได้ชัดว่าฟังก์ชั่นจะไม่ทิ้ง" ฉันไม่เห็นด้วย. noexceptเป็นส่วนหนึ่งของการทำงานของอินเตอร์เฟซ ; คุณไม่ควรเพิ่มเพียงเพราะการใช้งานในปัจจุบันของคุณไม่เกิดขึ้น ฉันไม่แน่ใจเกี่ยวกับคำตอบที่ถูกต้องสำหรับคำถามนี้ แต่ฉันค่อนข้างมั่นใจว่าการทำงานของคุณจะเกิดขึ้นในวันนี้ไม่มีอะไรเกี่ยวข้องกับมัน ...
Nemo

133

ขณะที่ผมเก็บซ้ำวันนี้: ความหมายแรก

เพิ่มnoexcept, noexcept(true)และnoexcept(false)เป็นที่แรกและสำคัญที่สุดเกี่ยวกับความหมาย โดยมีเงื่อนไขการเพิ่มประสิทธิภาพที่เป็นไปได้จำนวนหนึ่งเท่านั้น

ในฐานะที่เป็นโค้ดการอ่านโปรแกรมเมอร์การมีอยู่ของnoexceptนั้นคล้ายกับของconst: มันช่วยให้ฉันดีขึ้น grok สิ่งที่อาจจะหรืออาจจะไม่เกิดขึ้น ดังนั้นจึงเป็นการดีที่จะใช้เวลาคิดว่าคุณรู้หรือไม่ว่าฟังก์ชั่นนี้จะโยนหรือไม่ สำหรับการเตือนความจำการจัดสรรหน่วยความจำแบบไดนามิกทุกชนิดอาจส่งผล


ตกลงตอนนี้ไปสู่การเพิ่มประสิทธิภาพที่เป็นไปได้

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

คอมไพเลอร์อาจจะเป็นเพียงโกนบิตของไขมัน (อาจ) จากการจัดการข้อมูลข้อยกเว้นเพราะมันมีการคำนึงถึงความจริงที่คุณอาจจะโกหก หากฟังก์ชั่นที่ทำเครื่องหมายnoexceptไม่โยนstd::terminateจะมีการเรียกใช้

ความหมายเหล่านี้ถูกเลือกด้วยเหตุผลสองประการ:

  • ได้รับประโยชน์ทันทีnoexceptแม้ในขณะที่การพึ่งพาไม่ได้ใช้มันแล้ว (ความเข้ากันได้ย้อนหลัง)
  • อนุญาตให้สเปคของnoexceptเมื่อเรียกฟังก์ชั่นที่อาจโยนในทางทฤษฎี แต่ไม่คาดว่าจะมีข้อโต้แย้งที่กำหนด

2
บางทีฉันไร้เดียงสา แต่ฉันนึกภาพฟังก์ชั่นที่เรียกใช้เฉพาะnoexceptฟังก์ชั่นไม่จำเป็นต้องทำอะไรพิเศษเพราะข้อยกเว้นใด ๆ ที่อาจเกิดขึ้นterminateก่อนที่พวกเขาจะมาถึงระดับนี้ สิ่งนี้แตกต่างอย่างมากจากการจัดการและเผยแพร่bad_allocข้อยกเว้น

8
ใช่มันเป็นไปได้ที่จะกำหนด noexcept อย่างที่คุณแนะนำ แต่มันจะเป็นคุณสมบัติที่ใช้ไม่ได้จริงๆ ฟังก์ชั่นหลายตัวสามารถใช้งานได้หากไม่มีเงื่อนไขบางอย่างและคุณไม่สามารถเรียกมันได้แม้ว่าคุณจะรู้ว่าเป็นไปตามเงื่อนไขก็ตาม ตัวอย่างเช่นฟังก์ชั่นใด ๆ ที่อาจส่งผลให้ std :: invalid_argument
tr3w

3
@MatthieuM สายเกินไปสำหรับการตอบกลับ แต่อย่างไรก็ตาม ฟังก์ชั่นที่ไม่มีเครื่องหมายยกเว้นสามารถเรียกฟังก์ชั่นอื่น ๆ ที่สามารถโยนได้สัญญาคือฟังก์ชั่นนี้จะไม่ปล่อยข้อยกเว้นนั่นคือพวกมันต้องจัดการข้อยกเว้นด้วยตัวเอง!
Honf

4
ฉันตอบคำตอบนี้นานมากแล้ว แต่เมื่ออ่านและคิดอีกแล้วฉันมีความเห็น / คำถาม "Move semantics" เป็นเพียงตัวอย่างเดียวที่ฉันเคยเห็นใครให้ซึ่งnoexceptมีประโยชน์อย่างชัดเจน / ความคิดที่ดี ฉันเริ่มคิดที่จะย้ายการก่อสร้างการย้ายที่ได้รับมอบหมายและการแลกเปลี่ยนเป็นเพียงกรณีเดียวที่มี ... คุณรู้จักผู้อื่นหรือไม่
Nemo

5
@Nemo: ในไลบรารีมาตรฐานอาจเป็นเพียงคนเดียวอย่างไรก็ตามมันแสดงหลักการที่สามารถนำกลับมาใช้ที่อื่นได้ การดำเนินการย้ายเป็นการดำเนินการที่ทำให้บางรัฐอยู่ใน "Limbo" ชั่วคราวและเฉพาะเมื่อมีnoexceptคนอาจใช้มันอย่างมั่นใจบนชิ้นส่วนของข้อมูลที่สามารถเข้าถึงได้หลังจากนั้น ฉันเห็นความคิดนี้ถูกใช้ที่อื่น แต่ไลบรารี่มาตรฐานค่อนข้างบางใน C ++ และใช้เพื่อเพิ่มประสิทธิภาพสำเนาขององค์ประกอบที่ฉันคิดเท่านั้น
Matthieu M.

77

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

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

คุณถามตัวอย่างที่เฉพาะเจาะจง พิจารณารหัสนี้:

void foo(int x) {
    try {
        bar();
        x = 5;
        // Other stuff which doesn't modify x, but might throw
    } catch(...) {
        // Don't modify x
    }

    baz(x); // Or other statement using x
}

กราฟการไหลของฟังก์ชั่นนี้จะแตกต่างกันหากbarมีการระบุไว้noexcept(ไม่มีวิธีสำหรับการดำเนินการที่จะข้ามไปมาระหว่างจุดสิ้นสุดbarและคำสั่ง catch) เมื่อติดป้ายว่าnoexceptคอมไพเลอร์มั่นใจว่าค่าของ x คือ 5 ระหว่างฟังก์ชัน baz - บล็อก x = 5 ถูกกล่าวว่า "ครอง" บล็อก baz (x) โดยไม่มีขอบจากbar()คำสั่ง catch

จากนั้นสามารถทำสิ่งที่เรียกว่า "การขยายพันธุ์คงที่" เพื่อสร้างรหัสที่มีประสิทธิภาพมากขึ้น ถ้านี่คือ baz inline คำสั่งที่ใช้ x อาจมีค่าคงที่และสิ่งที่เคยเป็นการประเมินผลแบบรันไทม์สามารถเปลี่ยนเป็นการประเมินผลแบบคอมไพล์เวลา ฯลฯ

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


3
สิ่งนี้ทำให้เกิดความแตกต่างในทางปฏิบัติจริงหรือไม่? ตัวอย่างมีการวางแผนเพราะไม่มีอะไรมาก่อนx = 5สามารถโยน หากส่วนนั้นของtryบล็อกนั้นมีจุดประสงค์ใด ๆ เหตุผลก็จะไม่เกิดขึ้น
Potatoswatter

7
ฉันจะบอกว่ามันสร้างความแตกต่างอย่างแท้จริงในฟังก์ชั่นการเพิ่มประสิทธิภาพซึ่งมีบล็อก try / catch ตัวอย่างที่ฉันมอบให้แม้ถูกวางแผน แต่ก็ไม่ละเอียดถี่ถ้วน จุดที่ใหญ่กว่าคือ noexcept (เช่นคำสั่ง Throw () ก่อนหน้า) ช่วยให้การคอมไพล์สร้างกราฟการไหลที่เล็กลง (ขอบน้อยกว่าบล็อกน้อยกว่า) ซึ่งเป็นส่วนพื้นฐานของการปรับให้เหมาะสมหลายแบบ
เทอร์รี่ Mahaffey

คอมไพเลอร์จะรับรู้ได้อย่างไรว่ารหัสสามารถโยนข้อยกเว้นได้? และการเข้าถึงอาร์เรย์ถือเป็นข้อยกเว้นที่เป็นไปได้หรือไม่
Tomas Kubes

3
@ qub1n หากคอมไพเลอร์สามารถเห็นเนื้อความของฟังก์ชั่นก็สามารถมองหาthrowคำสั่งที่ชัดเจนหรือสิ่งอื่น ๆ เช่นnewที่สามารถโยน noexceptถ้าคอมไพเลอร์ไม่สามารถมองเห็นร่างกายแล้วมันต้องขึ้นอยู่กับการมีหรือไม่มีของ การเข้าถึงอาร์เรย์ธรรมดาโดยทั่วไปจะไม่สร้างข้อยกเว้น (C ++ ไม่มีการตรวจสอบขอบเขต) ดังนั้นไม่การเข้าถึงอาร์เรย์จะไม่ทำให้คอมไพเลอร์คิดว่าฟังก์ชันส่งข้อยกเว้นเพียงอย่างเดียว (การเข้าถึงนอกขอบเขตคือ UB ไม่ใช่ข้อยกเว้นที่รับประกัน)
cdhowie

@cdhowie " มันต้องขึ้นอยู่กับการมีหรือไม่มีของ noexcept " หรือการปรากฏตัวของthrow()ใน pre-noexcept c ++
curiousguy

57

noexceptสามารถปรับปรุงประสิทธิภาพการทำงานบางอย่างได้อย่างมาก นี้ไม่ได้เกิดขึ้นในระดับของการสร้างรหัสเครื่องโดยรวบรวม แต่โดยการเลือกอัลกอริทึมที่มีประสิทธิภาพมากที่สุดในขณะที่คนอื่น ๆ std::move_if_noexceptที่กล่าวถึงคุณจะเลือกนี้โดยใช้ฟังก์ชั่น ตัวอย่างเช่นการเติบโตของstd::vector(เช่นเมื่อเราโทรreserve) จะต้องให้การรับประกันความปลอดภัยยกเว้นที่แข็งแกร่ง ถ้ามันรู้ว่าTตัวสร้างการเคลื่อนไหวนั้นไม่ได้โยนมันก็สามารถเคลื่อนย้ายทุกองค์ประกอบได้ มิฉะนั้นก็จะต้องคัดลอกTs นี้ได้รับการอธิบายในรายละเอียดในโพสต์นี้


4
ภาคผนวก: นั่นหมายความว่าถ้าคุณกำหนดตัวสร้างการย้ายหรือตัวดำเนินการกำหนดค่าการย้ายเพิ่มnoexceptให้กับพวกเขา (ถ้ามี)! ฟังก์ชันการย้ายสมาชิกที่กำหนดโดยนัยได้noexceptเพิ่มเข้าไปในฟังก์ชันเหล่านั้นโดยอัตโนมัติ (ถ้ามี)
mucaho

33

เมื่อใดที่ฉันสามารถแนบเนียนสังเกตการปรับปรุงประสิทธิภาพหลังจากใช้noexcept? โดยเฉพาะอย่างยิ่งให้ตัวอย่างของรหัสที่คอมไพเลอร์ C ++ สามารถสร้างรหัสเครื่องที่ดีขึ้นหลังจากการเพิ่ม noexcept

เอ่อไม่เคยเหรอ? ไม่เคยมีเวลา? ไม่เคย

noexceptมีไว้สำหรับการปรับแต่งประสิทธิภาพของคอมไพเลอร์ในวิธีเดียวกันconstกับการปรับแต่งประสิทธิภาพของคอมไพเลอร์ นั่นคือเกือบจะไม่เคย

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

แม่เหมือนmove_if_noexceptจะตรวจสอบว่าตัวสร้างการย้ายจะถูกกำหนดด้วยnoexceptและจะกลับมาconst&แทนที่จะเป็น&&ชนิดที่ถ้ามันไม่ได้ มันเป็นวิธีการบอกว่าจะย้ายถ้ามันปลอดภัยมากที่จะทำ

โดยทั่วไปแล้วคุณควรใช้noexceptเมื่อคุณคิดว่ามันจริงจะเป็นประโยชน์ที่จะทำเช่นนั้น บางรหัสจะใช้เส้นทางที่แตกต่างกันหากis_nothrow_constructibleเป็นจริงสำหรับประเภทนั้น หากคุณใช้รหัสที่จะทำเช่นนั้นคุณสามารถnoexceptสร้าง Constructor ได้อย่างเหมาะสม

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


13
อย่างเคร่งครัดmove_if_noexceptจะไม่ส่งคืนสำเนา แต่จะส่งคืนการอ้างอิง lvalue const มากกว่าการอ้างอิง rvalue โดยทั่วไปแล้วจะทำให้ผู้โทรทำสำเนาแทนการย้าย แต่move_if_noexceptไม่ได้ทำการคัดลอก มิฉะนั้นคำอธิบายที่ดี
Jonathan Wakely

12
+1 โจนาธาน ปรับขนาดเวกเตอร์, noexceptตัวอย่างเช่นจะย้ายวัตถุแทนการคัดลอกพวกเขาหากคอนสตรัคย้าย เพื่อให้ "ไม่เคย" ไม่เป็นความจริง
mfontanini

4
ฉันหมายความว่าคอมไพเลอร์จะสร้างรหัสที่ดีขึ้นในสถานการณ์นั้น OP ขอตัวอย่างที่คอมไพเลอร์สามารถสร้างแอปพลิเคชั่นที่เหมาะสมที่สุดได้ ดูเหมือนว่าจะเป็นกรณีนี้ (แม้ว่าจะไม่ใช่การเพิ่มประสิทธิภาพคอมไพเลอร์ )
mfontanini

7
@mfontanini: คอมไพเลอร์สร้างโค้ดที่ดีกว่าเท่านั้นเพราะคอมไพเลอร์ถูกบังคับให้คอมไพล์รหัสต่างๆ มันเพียง แต่ทำงานเพราะstd::vectorถูกเขียนขึ้นเพื่อบังคับให้คอมไพเลอร์จะรวบรวมที่แตกต่างกันรหัส มันไม่เกี่ยวกับคอมไพเลอร์ที่ตรวจจับบางอย่าง มันเกี่ยวกับรหัสผู้ใช้ที่ตรวจจับบางอย่าง
Nicol Bolas

3
สิ่งคือฉันไม่สามารถหา "การเพิ่มประสิทธิภาพของคอมไพเลอร์" ในคำพูดที่จุดเริ่มต้นของคำตอบของคุณ ดังที่ @ChristianRau กล่าวว่าคอมไพเลอร์สร้างรหัสที่มีประสิทธิภาพมากขึ้นไม่ว่าอะไรจะมาจากการเพิ่มประสิทธิภาพนั้น ท้ายที่สุดคอมไพเลอร์กำลังสร้างโค้ดที่มีประสิทธิภาพมากขึ้นใช่ไหม PS: ฉันไม่เคยบอกว่าเป็นการเพิ่มประสิทธิภาพของคอมไพเลอร์ฉันยังพูดว่า "มันไม่ใช่การเพิ่มประสิทธิภาพของคอมไพเลอร์"
mfontanini

22

ในคำพูดของBjarne ( ภาษาการเขียนโปรแกรม C ++, รุ่นที่ 4 , หน้า 366):

ในกรณีที่การยกเลิกเป็นการตอบสนองที่ยอมรับได้ข้อยกเว้นที่ไม่ถูกตรวจจับจะทำให้บรรลุผลนั้นเพราะมันจะกลายเป็นการเรียกยุติ () (§13.5.2.5) นอกจากนี้ตัวnoexceptระบุ (§13.5.1.1) สามารถทำให้ความปรารถนานั้นชัดเจนได้

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

double compute(double x) noexcept;     {
    string s = "Courtney and Anya";
    vector<double> tmp(10);
    // ...
}

std::bad_allocตัวสร้างเวกเตอร์อาจล้มเหลวในหน่วยความจำสำหรับการซื้อคู่ผสมสิบและโยน ในกรณีนั้นโปรแกรมจะยุติการทำงาน มันสิ้นสุดลงโดยไม่มีเงื่อนไขโดยการเรียกใช้std::terminate()(§30.4.1.3) มันไม่ได้เรียกใช้ destructors จากฟังก์ชั่นการโทร มันคือการดำเนินการที่กำหนดว่า destructors จากขอบเขตระหว่าง throwและnoexcept(เช่นสำหรับในการคำนวณ ()) จะถูกเรียก โปรแกรมกำลังจะยุติดังนั้นเราไม่ควรพึ่งพาวัตถุใด ๆ โดยการเพิ่มตัวnoexceptระบุเราระบุว่ารหัสของเราไม่ได้ถูกเขียนขึ้นเพื่อรับมือกับการขว้าง


2
คุณมีแหล่งที่มาสำหรับคำพูดนี้หรือไม่?
Anton Golov

5
@AntonGolov "ภาษาการเขียนโปรแกรม C ++ รุ่นที่ 4" หน้า 366
Rusty Shackleford

สิ่งนี้ฟังดูราวกับว่าฉันควรเพิ่มnoexceptทุกครั้งยกเว้นว่าฉันต้องการดูแลข้อยกเว้นอย่างชัดเจน ขอให้เป็นจริงข้อยกเว้นส่วนใหญ่นั้นไม่น่าจะเป็นไปได้และ / หรือร้ายแรงมากจนการช่วยชีวิตนั้นไม่สมเหตุสมผลหรือเป็นไปได้ เช่นในตัวอย่างที่ยกมาหากการจัดสรรล้มเหลวแอปพลิเคชันจะไม่สามารถทำงานได้อย่างถูกต้อง
Neonit

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

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

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

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

  1. สำหรับสถานการณ์ใดที่ฉันควรระวังเกี่ยวกับการใช้ noexcept มากขึ้นและสำหรับสถานการณ์ใดที่ฉันสามารถหลีกเลี่ยง noexcept โดยนัย (false) ได้

มีฟังก์ชั่นสี่ประเภทที่คุณควรมีสมาธิเพราะมันอาจจะมีผลกระทบมากที่สุด:

  1. การดำเนินการย้าย (ตัวดำเนินการกำหนดค่าย้ายและตัวสร้างการย้าย)
  2. การดำเนินการแลกเปลี่ยน
  3. deallocators หน่วยความจำ (ตัวดำเนินการลบตัวดำเนินการลบ [])
  4. destructors (แม้ว่าสิ่งเหล่านี้จะเกิดขึ้นโดยปริยายnoexcept(true)เว้นแต่คุณจะสร้างมันขึ้นมาnoexcept(false))

โดยทั่วไปแล้วฟังก์ชั่นเหล่านี้น่าจะเป็นไปได้noexceptและเป็นไปได้มากว่าการใช้งานไลบรารีสามารถใช้ประโยชน์จากnoexceptคุณสมบัติได้ ตัวอย่างเช่นstd::vectorสามารถใช้การดำเนินการย้ายที่ไม่ใช่การขว้างปาได้โดยไม่ต้องเสียสละข้อยกเว้นที่แข็งแกร่ง มิฉะนั้นจะต้องถอยกลับไปที่การคัดลอกองค์ประกอบ (เช่นเดียวกับใน C ++ 98)

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

  1. เมื่อใดที่ฉันจะคาดหวังว่าจะสังเกตเห็นการปรับปรุงประสิทธิภาพตามจริงหลังจากใช้งาน noexcept โดยเฉพาะอย่างยิ่งให้ตัวอย่างของรหัสที่คอมไพเลอร์ C ++ สามารถสร้างรหัสเครื่องที่ดีขึ้นหลังจากการเพิ่ม noexcept

ข้อได้เปรียบของการnoexceptต่อต้านข้อกำหนดที่ไม่มีข้อยกเว้นหรือthrow()เป็นมาตรฐานที่ช่วยให้คอมไพเลอร์มีอิสระมากขึ้นเมื่อมันมาถึงกองคลี่คลาย แม้แต่ในthrow()กรณีคอมไพเลอร์ก็ต้องคลายสแต็คอย่างสมบูรณ์ (และต้องทำในลำดับย้อนกลับที่แน่นอนของการสร้างวัตถุ)

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

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

ฉันยังแนะนำหนังสือ Scott Meyers "Effective Modern C ++", "รายการที่ 14: ประกาศฟังก์ชั่นไม่มีข้อยกเว้นหากพวกเขาจะไม่ส่งข้อยกเว้น" สำหรับการอ่านเพิ่มเติม


ยังคงมีเหตุผลมากกว่านี้หากมีการใช้ข้อยกเว้นใน C ++ เหมือนใน Java ซึ่งคุณทำเครื่องหมายวิธีที่อาจใช้throwsคำหลักแทนการnoexceptลบ ฉันไม่สามารถรับตัวเลือกการออกแบบ C ++ บางอย่าง ...
doc

พวกเขาตั้งชื่อnoexceptเพราะthrowถูกยึดแล้ว เพียงแค่ใส่throwก็สามารถใช้งานได้เกือบจะในแบบที่คุณพูดยกเว้นพวกมันไม่ได้ออกแบบมาอย่างดีมันก็แทบไร้ประโยชน์ แต่ตอนนี้เรากำลังติดอยู่กับมันตั้งแต่ลบมันจะเป็นการเปลี่ยนแปลงที่มีประโยชน์เล็กน้อย ดังนั้นเป็นพื้นnoexcept throw_v2
AnorZaken

เป็นวิธีการที่throwไม่ได้มีประโยชน์หรือไม่
curiousguy

@crownguy "throw" ตัวเอง (สำหรับการโยนข้อยกเว้น) มีประโยชน์ แต่ "throw" เป็นตัวระบุข้อยกเว้นได้เลิกใช้แล้วและใน C ++ 17 ก็เอาออกไป สำหรับเหตุผลที่ตัวระบุข้อยกเว้นไม่มีประโยชน์ให้ดูคำถามนี้: stackoverflow.com/questions/88573/…
Philipp Claßen

1
@ PhilippClaßen throw()ระบุข้อยกเว้นไม่ได้ให้การรับประกันเช่นเดียวกับnothrow?
curiousguy

17

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

เมื่อคุณพูดว่า "ฉันรู้ว่า [พวกเขา] จะไม่มีวันโยน" คุณหมายถึงการตรวจสอบการใช้งานฟังก์ชั่นที่คุณรู้ว่าฟังก์ชั่นจะไม่โยน ฉันคิดว่าวิธีการนั้นอยู่ข้างใน

มันจะดีกว่าที่จะพิจารณาว่าฟังก์ชั่นอาจจะมีข้อยกเว้นที่จะเป็นส่วนหนึ่งของการออกแบบฟังก์ชั่น: มีความสำคัญเท่ากับรายการอาร์กิวเมนต์และวิธีการที่เป็น mutator (... const) ประกาศว่า "ฟังก์ชั่นนี้จะไม่ส่งข้อยกเว้น" เป็นข้อ จำกัด ในการใช้งาน การละเว้นไม่ได้หมายความว่าฟังก์ชันอาจส่งข้อยกเว้น หมายความว่ารุ่นปัจจุบันของฟังก์ชันและเวอร์ชันในอนาคตทั้งหมดอาจมีข้อยกเว้น มันเป็นข้อ จำกัด ที่ทำให้การใช้งานหนักขึ้น แต่วิธีการบางอย่างต้องมีข้อ จำกัด ที่จะเป็นประโยชน์ในทางปฏิบัติ สิ่งสำคัญที่สุดคือเพื่อให้สามารถเรียกได้จาก destructors แต่ยังสำหรับการใช้งานของ "ย้อนกลับ" รหัสในวิธีการที่ให้การรับประกันข้อยกเว้นที่แข็งแกร่ง


นี่คือคำตอบที่ดีที่สุด คุณให้การรับประกันแก่ผู้ใช้วิธีการของคุณซึ่งเป็นอีกวิธีหนึ่งที่บอกว่าคุณกำลัง จำกัด การใช้งานของคุณตลอดไป (เปลี่ยนรูปแบบใหม่) ขอบคุณสำหรับมุมมองที่กระจ่างแจ้ง
AnorZaken

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