อะไรคือประเด็นของ noreturn?


189

[dcl.attr.noreturn]ให้ตัวอย่างต่อไปนี้:

[[ noreturn ]] void f() {
    throw "error";
    // OK
}

แต่ผมไม่เข้าใจสิ่งที่เป็นจุดของเพราะประเภทการกลับมาของฟังก์ชั่นที่มีอยู่แล้ว[[noreturn]]void

ดังนั้นประเด็นของnoreturnคุณสมบัติคืออะไร? ควรใช้อย่างไร?


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

1
@MrLister OP อธิบายแนวคิดของ "การกลับมา" และ "การคืนค่า" เมื่อพิจารณาว่ามีการใช้ควบคู่กันเกือบทุกครั้งฉันคิดว่าความสับสนนั้นถูกต้อง
Slipp D. Thompson

คำตอบ:


209

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

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


5
สิ่งที่เกี่ยวกับฟังก์ชั่นเช่นexecveว่าไม่ควรกลับมา แต่จะทำได้ ? ควรมีแอตทริบิวต์noreturnหรือไม่
Kalrish

22
ไม่ไม่ควร - หากมีความเป็นไปได้ที่โฟลว์คอนโทรลจะกลับไปที่ผู้เรียกจะต้องไม่มีnoreturnแอตทริบิวต์ noreturnอาจใช้ได้เฉพาะเมื่อฟังก์ชันของคุณรับประกันว่าจะทำสิ่งที่ยุติโปรแกรมก่อนที่โฟลว์การควบคุมจะสามารถกลับไปที่ผู้โทรได้ - ตัวอย่างเช่นเนื่องจากคุณเรียก exit (), ยกเลิก (), ยืนยัน (0), ยืนยัน (0) ฯลฯ
RavuAlHemio

6
@ SlippD.Thompson หากการเรียกไปยังฟังก์ชั่น noreturn ถูกรวมอยู่ใน try-block โค้ดใด ๆ จาก catch block on จะถูกนับว่าสามารถเข้าถึงได้อีกครั้ง
sepp2k

2
@ sepp2k ยอดเยี่ยม ดังนั้นจึงเป็นไปไม่ได้ที่จะกลับมาผิดปกติ นั่นมีประโยชน์ ไชโย
Slipp D. Thompson

7
@ SlippDThompson ไม่เป็นไปไม่ได้ที่จะกลับมา noreturnการขว้างปายกเว้นไม่ได้ส่งคืนดังนั้นหากทุกเส้นทางพ่นแล้วมัน การจัดการข้อยกเว้นนั้นไม่เหมือนกับที่ส่งคืน รหัสใด ๆ ในการtryโทรหลังจากนั้นยังคงไม่สามารถเข้าถึงได้และหากไม่ได้voidรับมอบหมายหรือการใช้ค่าส่งคืนจะไม่เกิดขึ้น
Jon Hanna

63

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


29

หมายความว่าฟังก์ชั่นจะไม่สมบูรณ์ โฟลว์ควบคุมจะไม่ตีคำสั่งหลังจากการเรียกไปที่f():

void g() {
   f();
   // unreachable:
   std::cout << "No! That's impossible" << std::endl;
}

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


3
gcc / เสียงดังกราวไม่ให้คำเตือน
TemplateRex

4
@TemplateRex: คอมไพล์ด้วย-Wno-returnและคุณจะได้รับคำเตือน อาจไม่ใช่คนที่คุณคาดหวัง แต่อาจเพียงพอที่จะบอกคุณว่าผู้แปลมีความรู้เกี่ยวกับสิ่งที่[[noreturn]]เป็นและสามารถใช้ประโยชน์จากมันได้ (ฉันประหลาดใจเล็กน้อยที่-Wunreachable-codeไม่ได้เตะใน ... )
David Rodríguez - dribeas

3
@TemplateRex: ขออภัย-Wmissing-noreturnคำเตือนบอกเป็นนัยว่าการวิเคราะห์โฟลว์ระบุว่าstd::coutไม่สามารถเข้าถึงได้ ฉันไม่มี gcc ใหม่พอที่จะดูชุดประกอบที่สร้างขึ้น แต่ฉันจะไม่แปลกใจถ้าการเรียกสายoperator<<ถูกตัด
David Rodríguez - dribeas

1
ต่อไปนี้เป็นดัมพ์ประกอบ (-S -o - แฟล็กใน coliru) ปล่อยรหัส "ไม่สามารถเข้าถึง" ได้ น่าสนใจเพียงพอ-O1แล้วที่จะวางโค้ดที่ไม่สามารถเข้าถึงได้โดยไม่ต้อง[[noreturn]]บอกใบ้
TemplateRex

2
@TemplateRex: รหัสทั้งหมดอยู่ในหน่วยการแปลเดียวกันและมองเห็นได้ดังนั้นคอมไพเลอร์สามารถอนุมาน[[noreturn]]จากรหัส หากหน่วยการแปลนี้มีการประกาศฟังก์ชันที่กำหนดไว้ที่อื่นเท่านั้นคอมไพเลอร์จะไม่สามารถวางโค้ดนั้นได้เนื่องจากไม่ทราบว่าฟังก์ชันนั้นไม่ส่งคืน นั่นคือที่ที่แอตทริบิวต์ควรช่วยคอมไพเลอร์
David Rodríguez - dribeas

17

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

int f(bool b){
    if (b) {
        return 7;
    } else {
        abort();
    }
 }

หากมีการยกเลิก () ไม่ถูกทำเครื่องหมาย "noreturn" คอมไพเลอร์อาจเตือนเกี่ยวกับรหัสนี้ว่ามีเส้นทางที่ f ไม่คืนค่าจำนวนเต็มที่ตามที่คาดไว้ แต่เนื่องจาก abort () ถูกทำเครื่องหมายว่าไม่ส่งคืนจึงทราบว่ารหัสนั้นถูกต้อง


ตัวอย่างอื่น ๆ ทั้งหมดที่อยู่ในรายการใช้ฟังก์ชั่นโมฆะ - มันทำงานอย่างไรเมื่อคุณมีทั้ง [[no return]] directive และ non-void return type? คำสั่ง [[ไม่ส่งคืน]] มาเล่นเมื่อคอมไพเลอร์พร้อมที่จะเตือนเกี่ยวกับความเป็นไปได้ที่จะไม่กลับมาและเพิกเฉยต่อคำเตือนหรือไม่ ตัวอย่างเช่นคอมไพเลอร์ไปหรือไม่: "โอเคนี่คือฟังก์ชั่นที่ไม่เป็นโมฆะ" * คอมไพล์ต่อไป * "อึรหัสนี้อาจไม่ส่งคืนฉันควรเตือนผู้ใช้หรือไม่ *" ไม่เป็นไรฉันเห็นคำสั่งที่ไม่ส่งคืน ดำเนินต่อไป "
Raleigh L.

2
ฟังก์ชั่น noreturn ในตัวอย่างของฉันไม่ใช่ f () แต่ก็ยกเลิก () มันไม่สมเหตุสมผลเลยที่จะทำเครื่องหมายฟังก์ชั่นที่ไม่เป็นโมฆะ ฟังก์ชั่นที่บางครั้งส่งกลับค่าและบางครั้งกลับ (ตัวอย่างที่ดีคือ execve ()) ไม่สามารถทำเครื่องหมาย noreturn
Nadav Har'El

1
implicit-fallthrough เป็นอีกตัวอย่างหนึ่งเช่น: stackoverflow.com/questions/45129741/…
Ciro Santilli 法轮功冠状病病六四事件法轮功

11

พิมพ์ทฤษฎีพูดvoidคือสิ่งที่เรียกในภาษาอื่น ๆหรือunit topเทียบเท่าตรรกะของมันคือทรู ค่าใด ๆ สามารถนำไปใช้ได้อย่างถูกต้องตามกฎหมายvoid(ทุกประเภทเป็นประเภทย่อยvoid) คิดว่ามันเป็นชุด "จักรวาล"; มีการดำเนินงานในเรื่องธรรมดาที่จะไม่มีทุกvoidค่าในโลกดังนั้นจึงไม่มีการดำเนินการที่ถูกต้องในค่าของชนิด ใช้อีกวิธีหนึ่งโดยบอกคุณว่ามีบางสิ่งในชุดจักรวาลไม่ให้ข้อมูลใด ๆ - คุณรู้แล้ว ดังนั้นต่อไปนี้เป็นเสียง:

(void)5;
(void)foo(17); // whatever foo(17) does

แต่การมอบหมายด้านล่างไม่ใช่:

void raise();
void f(int y) {
    int x = y!=0 ? 100/y : raise(); // raise() returns void, so what should x be?
    cout << x << endl;
}

[[noreturn]]บนมืออื่น ๆ ที่เรียกว่าบางครั้งempty, Nothing, BottomหรือBotและเป็นเทียบเท่าตรรกะของเท็จ มันไม่มีค่าเลยและการแสดงออกของประเภทนี้สามารถส่งไปที่ (เช่นประเภทย่อย) ประเภทใด ๆ นี่คือชุดที่ว่างเปล่า โปรดทราบว่าหากมีคนบอกคุณว่า "คุณค่าของนิพจน์ foo () เป็นของเซตว่าง" มันเป็นข้อมูลที่มาก - มันบอกคุณว่านิพจน์นี้จะไม่ดำเนินการตามปกติให้เสร็จสมบูรณ์ มันจะยกเลิกโยนหรือแขวน voidมันเป็นตรงกันข้าม

ดังนั้นสิ่งต่อไปนี้ไม่สมเหตุสมผล (pseudo-C ++ เนื่องจากnoreturnไม่ใช่ประเภท C ++ ชั้นหนึ่ง)

void foo();
(noreturn)5; // obviously a lie; the expression 5 does "return"
(noreturn)foo(); // foo() returns void, and therefore returns

แต่การมอบหมายด้านล่างถูกต้องตามกฎหมายอย่างสมบูรณ์เนื่องจากthrowคอมไพเลอร์เข้าใจว่าจะไม่ส่งคืน:

void f(int y) {
    int x = y!=0 ? 100/y : throw exception();
    cout << x << endl;
}

ในโลกที่สมบูรณ์แบบคุณสามารถใช้noreturnเป็นค่าตอบแทนสำหรับฟังก์ชั่นraise()ด้านบน:

noreturn raise() { throw exception(); }
...
int x = y!=0 ? 100/y : raise();

Sadly C ++ ไม่อนุญาตให้ใช้อาจเป็นไปได้ว่าด้วยเหตุผลเชิงปฏิบัติ แต่จะให้ความสามารถในการใช้[[ noreturn ]]คุณสมบัติซึ่งช่วยให้แนวทางการปรับแต่งคอมไพเลอร์และคำเตือน


5
ไม่มีสิ่งใดที่จะถูกโยนvoidและvoidไม่เคยประเมินผลtrueหรือfalseหรือสิ่งอื่นใด
ชัดเจนมากขึ้น

5
เมื่อฉันพูด trueฉันไม่ได้หมายถึง "คุณค่าtrueของประเภทbool" แต่เป็นเหตุผลเชิงตรรกะดูการติดต่อของCurry-Howard
Elazar

8
ทฤษฎีประเภทนามธรรมซึ่งไม่เหมาะกับระบบการพิมพ์ของภาษาเฉพาะนั้นไม่เกี่ยวข้องเมื่อพูดถึงระบบการพิมพ์ของภาษานั้น คำถามในคำถาม :-) นั้นเกี่ยวกับ C ++ ไม่ใช่ทฤษฎีชนิด
Clear

8
(void)true;ถูกต้องสมบูรณ์แบบตามคำแนะนำ void(true)เป็นสิ่งที่แตกต่างอย่างสิ้นเชิง syntactically มันเป็นความพยายามที่จะสร้างวัตถุชนิดใหม่voidโดยการเรียกคอนสตรัคเตอร์ด้วยtrueเป็นอาร์กิวเมนต์; สิ่งนี้ล้มเหลวด้วยเหตุผลอื่น ๆ เนื่องจากvoidไม่ใช่ชั้นหนึ่ง
Elazar

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