Apa gunanya noreturn?

189

[dcl.attr.noreturn] memberikan contoh berikut:

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

tapi saya tidak mengerti apa gunanya [[noreturn]], karena tipe kembalinya fungsi sudah void.

Jadi, apa gunanya noreturnatribut itu? Bagaimana seharusnya digunakan?

BЈовић
sumber
1
Apa yang begitu penting tentang fungsi semacam ini (yang kemungkinan besar akan terjadi sekali dalam pelaksanaan program) yang pantas mendapatkan perhatian seperti itu? Bukankah ini situasi yang mudah dideteksi?
user666412
1
@MrLister OP mengacaukan konsep "kembali" dan "nilai kembali". Mengingat bagaimana mereka hampir selalu digunakan bersama-sama, saya pikir kebingungan dibenarkan.
Slipp D. Thompson

Jawaban:

209

Atribut noreturn seharusnya digunakan untuk fungsi yang tidak kembali ke pemanggil. Itu tidak berarti fungsi batal (yang memang kembali ke pemanggil - mereka hanya tidak mengembalikan nilai), tetapi fungsi di mana aliran kontrol tidak akan kembali ke fungsi panggilan setelah fungsi selesai (mis. Fungsi yang keluar dari aplikasi, loop selamanya atau membuang pengecualian seperti pada contoh Anda).

Ini dapat digunakan oleh kompiler untuk melakukan beberapa optimasi dan menghasilkan peringatan yang lebih baik. Sebagai contoh jika fmemiliki atribut noreturn, kompiler dapat memperingatkan Anda tentang g()kode mati ketika Anda menulis f(); g();. Demikian pula kompiler akan tahu untuk tidak memperingatkan Anda tentang pernyataan pengembalian yang hilang setelah panggilan ke f().

sepp2k
sumber
5
Bagaimana dengan fungsi seperti execveitu yang seharusnya tidak kembali tetapi bisa ? Haruskah ia memiliki atribut noreturn ?
Kalrish
22
Tidak, seharusnya tidak - jika ada kemungkinan aliran kontrol untuk kembali ke pemanggil, itu tidak boleh memiliki noreturnatribut. noreturnhanya dapat digunakan jika fungsi Anda dijamin untuk melakukan sesuatu yang menghentikan program sebelum aliran kontrol dapat kembali ke pemanggil - misalnya karena Anda memanggil keluar (), batalkan (), nyatakan (0), dll.
RavuAlHemio
7
@ SlippD.Thompson Jika panggilan ke fungsi noreturn dibungkus dalam blok-coba, kode apa pun dari blok tangkap aktif akan dihitung dapat dijangkau lagi.
sepp2k
2
@ sepp2k Keren. Jadi bukan tidak mungkin untuk kembali, hanya abnormal. Itu berguna. Bersulang.
Slipp D. Thompson
7
@ SlippD. Thompson tidak, tidak mungkin untuk kembali. Melempar pengecualian tidak kembali, jadi jika setiap jalur melempar maka itu noreturn. Menangani pengecualian itu tidak sama dengan mengembalikannya. Kode apa pun dalam trysetelah panggilan masih tidak dapat dijangkau, dan jika tidak voidmaka penugasan atau penggunaan nilai pengembalian tidak akan terjadi.
Jon Hanna
63

noreturntidak memberi tahu kompiler bahwa fungsi tidak mengembalikan nilai apa pun. Ini memberitahu kompiler bahwa aliran kontrol tidak akan kembali ke pemanggil . Hal ini memungkinkan kompiler untuk membuat berbagai optimisasi - ia tidak perlu menyimpan dan mengembalikan keadaan volatile apa pun di sekitar panggilan, dapat mematikan kode menghilangkan kode apa pun yang akan mengikuti panggilan, dll.

Stephen Canon
sumber
29

Ini berarti bahwa fungsi tidak akan selesai. Aliran kontrol tidak akan pernah mencapai pernyataan setelah panggilan ke f():

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

Informasi dapat digunakan oleh kompiler / pengoptimal dengan berbagai cara. Kompiler dapat menambahkan peringatan bahwa kode di atas tidak dapat dijangkau, dan dapat memodifikasi kode aktual g()dengan berbagai cara misalnya untuk mendukung kelanjutan.

David Rodríguez - dribeas
sumber
3
gcc / dentang tidak memberikan peringatan
TemplateRex
4
@TemplateRex: Kompilasi dengan -Wno-returndan Anda akan mendapatkan peringatan. Mungkin bukan yang Anda harapkan tetapi mungkin cukup untuk memberi tahu Anda bahwa kompiler memiliki pengetahuan tentang apa [[noreturn]]itu dan dapat mengambil keuntungan darinya. (Saya sedikit terkejut bahwa -Wunreachable-codeitu tidak masuk ...)
David Rodríguez - dribeas
3
@TemplateRex: Maaf -Wmissing-noreturn, peringatan menyiratkan bahwa analisis aliran menentukan bahwa std::couttidak dapat dijangkau. Saya tidak punya gcc yang cukup baru di tangan untuk melihat perakitan yang dihasilkan, tapi saya tidak akan terkejut jika panggilan untuk operator<<dijatuhkan
David Rodríguez - dribeas
1
Ini adalah dump assembly (-S -o - flags di coliru), memang menjatuhkan kode "unreachable". Cukup menarik, -O1sudah cukup untuk menjatuhkan kode yang tidak terjangkau tanpa [[noreturn]]petunjuk.
TemplateRex
2
@TemplateRex: Semua kode berada dalam unit terjemahan yang sama dan terlihat, sehingga kompiler dapat menyimpulkan [[noreturn]]dari kode. Jika unit terjemahan ini hanya memiliki deklarasi fungsi yang didefinisikan di tempat lain, kompiler tidak akan dapat melepaskan kode itu, karena tidak tahu bahwa fungsi tidak kembali. Di situlah atribut harus membantu kompiler.
David Rodríguez - dribeas
18

Jawaban sebelumnya dengan benar menjelaskan apa itu noreturn, tetapi bukan mengapa itu ada. Saya tidak berpikir komentar "optimisasi" adalah tujuan utama: Fungsi yang tidak kembali jarang dan biasanya tidak perlu dioptimalkan. Sebaliknya saya pikir alasan utama dari noreturn adalah untuk menghindari peringatan positif palsu. Sebagai contoh, pertimbangkan kode ini:

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

Seandainya batalkan () tidak ditandai "noreturn", kompiler mungkin telah memperingatkan tentang kode ini memiliki lintasan di mana f tidak mengembalikan integer seperti yang diharapkan. Tetapi karena batalkan () ditandai tidak kembali ia tahu kode itu benar.

Nadav Har'El
sumber
Semua contoh lain yang tercantum menggunakan fungsi void - bagaimana cara kerjanya ketika Anda memiliki kedua direktif [[tidak ada pengembalian]] dan jenis pengembalian yang tidak kosong? Apakah arahan [[tidak kembali]] hanya berlaku ketika kompiler bersiap untuk memperingatkan tentang kemungkinan tidak kembali dan mengabaikan peringatan? Sebagai contoh, apakah kompiler berjalan: "Oke, ini adalah fungsi yang tidak kosong." * terus mengkompilasi * "Oh sial, kode ini mungkin tidak kembali! Haruskah saya memperingatkan pengguna? *" Nevermind, saya melihat arahan no-return. Lanjutkan "
Raleigh L.
2
Fungsi noreturn dalam contoh saya bukan f (), ini dibatalkan (). Tidak masuk akal untuk menandai fungsi non-void noreturn. Fungsi yang terkadang mengembalikan nilai dan terkadang mengembalikan (contoh yang baik adalah execve ()) tidak dapat ditandai noreturn.
Nadav Har'El
1
implisit-fallthrough adalah contoh lain seperti: stackoverflow.com/questions/45129741/...
Ciro Santilli郝海东冠状病六四事件法轮功
12

Jenis berbicara secara teoritis, voidadalah apa yang disebut dalam bahasa lain unitatau top. Setara logisnya adalah Benar . Nilai apa pun dapat dilemparkan ke void(setiap jenis adalah subtipe dari void) secara sah . Pikirkan itu sebagai set "semesta"; tidak ada operasi yang sama untuk semua nilai di dunia, jadi tidak ada operasi yang valid pada nilai tipe void. Dengan kata lain, memberi tahu Anda bahwa sesuatu milik alam semesta tidak memberi Anda informasi apa pun - Anda sudah mengetahuinya. Jadi yang berikut adalah suara:

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

Namun tugas di bawah ini bukan:

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]], Di sisi lain, disebut kadang-kadang empty, Nothing, Bottomatau Botdan adalah setara logis dari False . Tidak memiliki nilai sama sekali, dan ekspresi tipe ini dapat dilemparkan ke (yaitu subtipe dari) tipe apa pun. Ini adalah set kosong. Perhatikan bahwa jika seseorang memberi tahu Anda "nilai ekspresi foo () milik set kosong" itu sangat informatif - ia memberi tahu Anda bahwa ungkapan ini tidak akan pernah menyelesaikan eksekusi normalnya; itu akan membatalkan, melempar atau menggantung. Ini adalah kebalikan dari void.

Jadi yang berikut ini tidak masuk akal (pseudo-C ++, karena noreturnbukan tipe C ++ kelas satu)

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

Tetapi penugasan di bawah ini benar-benar sah, karena throwdipahami oleh kompiler untuk tidak kembali:

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

Di dunia yang sempurna, Anda bisa menggunakan noreturnsebagai nilai balik untuk fungsi di raise()atas:

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

Sayangnya C ++ tidak memungkinkan, mungkin karena alasan praktis. Sebaliknya itu memberi Anda kemampuan untuk digunakan[[ noreturn ]] atribut yang membantu membimbing optimasi dan peringatan kompiler.

Elazar
sumber
5
Tidak ada yang bisa dilemparkan ke voiddan voidtidak pernah dievaluasi ke trueatau falseatau apa pun.
Clearer
6
Ketika saya mengatakan true, saya tidak bermaksud "nilai truedari tipe bool" tetapi pengertian logika, lihat korespondensi Curry-Howard
Elazar
8
Teori tipe abstrak yang tidak cocok dengan sistem tipe bahasa tertentu tidak relevan ketika membahas sistem tipe bahasa itu. Pertanyaannya, dalam pertanyaan :-), adalah tentang C ++, bukan tipe teori.
Clearer
8
(void)true;sangat valid, seperti jawabannya. void(true)adalah sesuatu yang sama sekali berbeda, secara sintaksis. Ini adalah upaya untuk membuat objek tipe baru voiddengan memanggil konstruktor dengan truesebagai argumen; ini gagal, antara lain, karena voidbukan kelas satu.
Elazar
2
Begitulah. Saya terbiasa menggunakan tipe (nilai) casting, bukan c style casting.
Clearer