Điểm của noreturn là gì?


189

[dcl.attr.noreturn] cung cấp ví dụ sau:

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

nhưng tôi không hiểu ý của nó là gì [[noreturn]], vì kiểu trả về của hàm đã có void.

Vì vậy, điểm của noreturnthuộc tính là gì? Làm thế nào nó được sử dụng?


1
Điều gì là rất quan trọng về loại funciton này (điều đó rất có thể sẽ xảy ra một lần trong khi thực hiện chương trình) mà đáng được quan tâm như vậy? Đây không phải là một tình huống dễ phát hiện sao?
dùng666412

1
@MrLister OP kết hợp các khái niệm về việc trả lại giá trị của Google và giá trị trả lại của Google. Dựa vào cách chúng gần như luôn được sử dụng song song, tôi nghĩ rằng sự nhầm lẫn là hợp lý.
Slipp D. Thompson

Câu trả lời:


209

Thuộc tính noreturn được cho là được sử dụng cho các hàm không trả về cho người gọi. Điều đó không có nghĩa là các hàm void (trả về cho người gọi - chúng chỉ không trả về giá trị), nhưng các hàm trong đó luồng điều khiển sẽ không trở về chức năng gọi sau khi chức năng kết thúc (ví dụ: các chức năng thoát khỏi ứng dụng, lặp mãi mãi hoặc ném ngoại lệ như trong ví dụ của bạn).

Điều này có thể được sử dụng bởi các trình biên dịch để thực hiện một số tối ưu hóa và tạo ra các cảnh báo tốt hơn. Ví dụ: nếu fcó thuộc tính noreturn, trình biên dịch có thể cảnh báo bạn về g()việc mã chết khi bạn viết f(); g();. Tương tự, trình biên dịch sẽ biết không cảnh báo bạn về các câu lệnh return bị thiếu sau khi gọi đến f().


5
Những gì về một chức năng như execverằng không nên trở lại nhưng có thể ? Nó có nên có thuộc tính noreturn ?
Kalrish

22
Không, không nên - nếu có khả năng luồng điều khiển quay lại người gọi, thì nó không được có noreturnthuộc tính. noreturnchỉ có thể được sử dụng nếu chức năng của bạn được đảm bảo thực hiện điều gì đó chấm dứt chương trình trước khi luồng điều khiển có thể quay lại người gọi - ví dụ vì bạn gọi exit (), abort (), khẳng định (0), v.v.
RavuAlHemio

6
@ SlippD.Thndry Nếu một lệnh gọi đến hàm noreturn được gói trong một khối thử, bất kỳ mã nào từ khối bắt trên sẽ được tính là có thể truy cập lại được.
sepp2k

2
@ sepp2k Mát mẻ. Vì vậy, nó không thể trở lại, chỉ là bất thường. Điều đó hữu ích. Chúc mừng.
Slipp D. Thompson

7
@ SlippD.Thndry không, không thể quay lại. Ném một ngoại lệ là không quay trở lại, vì vậy nếu mọi con đường ném thì đó là noreturn. Xử lý ngoại lệ đó không giống như nó đã trở lại. Bất kỳ mã nào trong trycuộc gọi sau cuộc gọi vẫn không thể truy cập được và nếu không voidthì việc chuyển nhượng hoặc sử dụng giá trị trả lại sẽ không xảy ra.
Jon Hanna

62

noreturnkhông nói với trình biên dịch rằng hàm không trả về bất kỳ giá trị nào. Nó báo cho trình biên dịch rằng luồng điều khiển sẽ không trả về cho người gọi . Điều này cho phép trình biên dịch thực hiện một loạt các tối ưu hóa - nó không cần lưu và khôi phục bất kỳ trạng thái biến động nào xung quanh cuộc gọi, nó có thể loại bỏ mã chết bất kỳ mã nào theo sau cuộc gọi, v.v.


29

Nó có nghĩa là chức năng sẽ không hoàn thành. Luồng điều khiển sẽ không bao giờ nhấn câu lệnh sau lệnh gọi tới f():

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

Thông tin có thể được sử dụng bởi trình biên dịch / trình tối ưu hóa theo nhiều cách khác nhau. Trình biên dịch có thể thêm một cảnh báo rằng mã ở trên là không thể truy cập được và nó có thể sửa đổi mã thực tế g()theo các cách khác nhau để hỗ trợ các phần tiếp theo.



4
@TemplateRex: Biên dịch với -Wno-returnvà bạn sẽ nhận được cảnh báo. Có lẽ không phải là người bạn đang mong đợi nhưng có lẽ đủ để nói với bạn rằng trình biên dịch có kiến ​​thức về những gì [[noreturn]]và nó có thể tận dụng lợi thế của nó. (Tôi hơi ngạc nhiên khi -Wunreachable-codekhông đá vào ...)
David Rodríguez - dribeas

3
@TemplateRex: Xin lỗi -Wmissing-noreturn, cảnh báo ngụ ý rằng phân tích dòng chảy xác định rằng std::coutkhông thể truy cập được. Tôi không có một gcc đủ mới trong tay để xem xét tổ hợp đã tạo, nhưng tôi sẽ không ngạc nhiên nếu cuộc gọi operator<<bị hủy
David Rodríguez - dribeas

1
Đây là một bãi chứa lắp ráp (-S -o - cờ trong coliru), thực sự làm giảm mã "không thể truy cập". Thật thú vị, -O1đã đủ để bỏ mã không thể truy cập mà không có [[noreturn]]gợi ý.
TemplateRex

2
@TemplateRex: Tất cả các mã nằm trong cùng một đơn vị dịch và hiển thị, do đó trình biên dịch có thể suy ra [[noreturn]]từ mã. Nếu đơn vị dịch thuật này chỉ có một khai báo của hàm được định nghĩa ở một nơi khác, trình biên dịch sẽ không thể bỏ mã đó, vì nó không biết rằng hàm đó không trả về. Đó là nơi thuộc tính sẽ giúp trình biên dịch.
David Rodríguez - dribeas

17

Các câu trả lời trước đã giải thích chính xác noreturn là gì, nhưng không hiểu tại sao nó tồn tại. Tôi không nghĩ các bình luận "tối ưu hóa" là mục đích chính: Các hàm không trả về là hiếm và thường không cần phải tối ưu hóa. Thay vào đó, tôi nghĩ rằng nhà tù chính của noreturn là để tránh những cảnh báo dương tính giả. Ví dụ, hãy xem xét mã này:

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

Nếu hủy bỏ () không được đánh dấu là "noreturn", trình biên dịch có thể đã cảnh báo về mã này có đường dẫn trong đó f không trả về một số nguyên như mong đợi. Nhưng vì abort () được đánh dấu không trả về nên nó biết mã là chính xác.


Tất cả các ví dụ khác được liệt kê đều sử dụng các hàm void - nó hoạt động như thế nào khi bạn có cả lệnh [[no return]] và kiểu trả về không void? Có phải lệnh [[không trả lại]] chỉ phát huy tác dụng khi trình biên dịch sẵn sàng cảnh báo về khả năng không quay lại và bỏ qua cảnh báo không? Ví dụ, trình biên dịch có đi không: "Được rồi, đây là một hàm không trống." * tiếp tục biên dịch * "Ôi trời ơi, mã này có thể không quay lại! Tôi có nên cảnh báo người dùng không? *" Nevermind, tôi thấy lệnh không trả lại. Tiếp tục "
Raleigh L.

2
Hàm noreturn trong ví dụ của tôi không phải là f (), nó hủy bỏ (). Không có ý nghĩa gì khi đánh dấu một hàm không trống. Một hàm đôi khi trả về một giá trị và đôi khi trả về (một ví dụ tốt là execve ()) không thể được đánh dấu noreturn.
Nadav Har'El


11

Gõ theo lý thuyết, voidlà những gì được gọi trong các ngôn ngữ khác unithoặc top. Tương đương logic của nó là True . Bất kỳ giá trị nào cũng có thể được sử dụng một cách hợp pháp void(mỗi loại là một kiểu con void). Hãy nghĩ về nó như "vũ trụ" được thiết lập; không có hoạt động chung cho tất cả các giá trị trên thế giới, vì vậy không có hoạt động hợp lệ trên một giá trị loại void. Nói cách khác, nói với bạn rằng một cái gì đó thuộc về vũ trụ không cung cấp cho bạn bất kỳ thông tin nào - bạn đã biết nó rồi. Vì vậy, sau đây là âm thanh:

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

Nhưng bài tập dưới đây không phải là:

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]]Mặt khác, được gọi là đôi khi empty, Nothing, Bottomhay Botvà là tương đương logic của False . Nó không có giá trị nào cả, và một biểu thức của loại này có thể được chuyển thành (tức là kiểu con của) bất kỳ loại nào. Đây là bộ trống. Lưu ý rằng nếu ai đó nói với bạn "giá trị của biểu thức foo () thuộc về tập hợp trống" thì nó có nhiều thông tin - nó cho bạn biết rằng biểu thức này sẽ không bao giờ hoàn thành việc thực hiện bình thường; Nó sẽ hủy bỏ, ném hoặc treo. Nó là hoàn toàn ngược lại void.

Vì vậy, những điều sau đây không có ý nghĩa (pseudo-C ++, vì noreturnkhông phải là loại C ++ hạng nhất)

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

Nhưng bài tập dưới đây là hoàn toàn hợp pháp, throwdo trình biên dịch hiểu là không trả về:

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

Trong một thế giới hoàn hảo, bạn có thể sử dụng noreturnlàm giá trị trả về cho hàm raise()trên:

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

Đáng buồn là C ++ không cho phép nó, có lẽ vì lý do thực tế. Thay vào đó, nó cung cấp cho bạn khả năng sử dụng [[ noreturn ]]thuộc tính giúp hướng dẫn tối ưu hóa và cảnh báo trình biên dịch.


5
Không có gì có thể được đúc để voidvoidkhông bao giờ để đánh giá truehoặc falsehoặc bất cứ điều gì khác.
Rõ ràng hơn

5
Khi tôi nói true, tôi không có nghĩa là "giá trị truecủa loại bool" mà là ý nghĩa logic, xem thư từ của Curry-Howard
Elazar

8
Lý thuyết loại trừu tượng không phù hợp với hệ thống loại của một ngôn ngữ cụ thể là không liên quan khi thảo luận về hệ thống loại của ngôn ngữ đó. Câu hỏi, trong câu hỏi :-), là về C ++, không phải lý thuyết loại.
Rõ ràng hơn

8
(void)true;là hoàn toàn hợp lệ, như câu trả lời cho thấy. void(true)là một cái gì đó hoàn toàn khác nhau, về mặt cú pháp. Đó là một nỗ lực để tạo một đối tượng kiểu mới voidbằng cách gọi một hàm tạo với truetư cách là một đối số; Điều này thất bại, trong số các lý do khác, bởi vì voidkhông phải là lớp học đầu tiên.
Elazar

2
Nên nó là. Tôi đã quen với việc sử dụng kiểu đúc (giá trị), không phải kiểu đúc c.
Rõ ràng hơn
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.