Kapan saya harus menggunakan noexcept?

509

Kata noexceptkunci dapat diterapkan secara tepat ke banyak tanda tangan fungsi, tetapi saya tidak yakin kapan saya harus mempertimbangkan menggunakannya dalam praktik. Berdasarkan apa yang telah saya baca sejauh ini, penambahan menit terakhir dari noexcepttampaknya mengatasi beberapa masalah penting yang muncul ketika memindahkan konstruktor. Namun, saya masih belum dapat memberikan jawaban yang memuaskan untuk beberapa pertanyaan praktis yang membuat saya membaca lebih lanjut noexcept.

  1. Ada banyak contoh fungsi yang saya tahu tidak akan pernah dilempar, tetapi kompiler tidak dapat menentukannya sendiri. Haruskah saya menambahkan noexceptdeklarasi fungsi dalam semua kasus seperti itu?

    Harus memikirkan apakah saya perlu menambahkan atau tidak noexceptsetelah setiap pernyataan fungsi akan sangat mengurangi produktivitas programmer (dan terus terang, akan menyebalkan). Untuk situasi apa saya harus lebih berhati-hati dalam penggunaannya noexcept, dan untuk situasi apa saya bisa lolos dari yang tersirat noexcept(false)?

  2. Kapan saya bisa secara realistis berharap untuk mengamati peningkatan kinerja setelah menggunakan noexcept? Secara khusus, berikan contoh kode yang kompiler C ++ dapat menghasilkan kode mesin yang lebih baik setelah penambahan noexcept.

    Secara pribadi, saya peduli noexceptkarena meningkatnya kebebasan yang diberikan kepada kompiler untuk secara aman menerapkan beberapa jenis optimasi. Apakah kompiler modern mengambil keuntungan noexceptdengan cara ini? Jika tidak, dapatkah saya mengharapkan beberapa dari mereka melakukannya dalam waktu dekat?

batal-pointer
sumber
40
Kode yang menggunakan move_if_nothrow(atau whatchamacallit) akan melihat peningkatan kinerja jika ada ctor langkah noexcept.
R. Martinho Fernandes
5
Ini move_if_noexcept.
Nikos

Jawaban:

180

Saya pikir masih terlalu dini untuk memberikan jawaban "praktik terbaik" untuk ini karena belum ada cukup waktu untuk menggunakannya dalam praktik. Jika ini ditanyakan tentang specifier pelemparan tepat setelah mereka keluar maka jawabannya akan sangat berbeda untuk sekarang.

Harus memikirkan apakah saya perlu menambahkan atau tidak noexceptsetelah setiap pernyataan fungsi akan sangat mengurangi produktivitas programmer (dan terus terang, akan menyebalkan).

Nah, kemudian gunakan ketika sudah jelas bahwa fungsi tidak akan pernah membuang.

Kapan saya bisa secara realistis berharap untuk mengamati peningkatan kinerja setelah menggunakan noexcept? [...] Secara pribadi, saya peduli noexceptkarena meningkatnya kebebasan yang diberikan kepada kompiler untuk secara aman menerapkan beberapa jenis optimasi.

Sepertinya keuntungan pengoptimalan terbesar adalah dari pengoptimalan pengguna, bukan pengompilasi karena kemungkinan memeriksa noexceptdan membebani secara berlebihan. Sebagian besar kompiler mengikuti metode penanganan pengecualian tanpa-penalti-jika-Anda-jangan-lempar, jadi saya ragu itu akan banyak berubah (atau apa pun) pada tingkat kode mesin kode Anda, meskipun mungkin mengurangi ukuran biner dengan menghapus menangani kode.

Menggunakan noexceptdi empat besar (konstruktor, tugas, bukan destruktor seperti yang sudah mereka lakukan noexcept) kemungkinan akan menyebabkan peningkatan terbaik karena noexceptcek 'umum' dalam kode templat seperti dalam stdwadah. Misalnya, std::vectortidak akan menggunakan gerakan kelas Anda kecuali jika ditandai noexcept(atau kompilator dapat menyimpulkannya).

Pubby
sumber
6
Saya pikir std::terminatetriknya masih mematuhi model Zero-Cost. Yaitu, kebetulan bahwa kisaran instruksi dalam noexceptfungsi dipetakan untuk memanggil std::terminatejika throwdigunakan sebagai ganti tumpukan yang dibuka. Karena itu saya ragu memiliki lebih banyak overhead daripada pelacakan pengecualian reguler.
Matthieu M.
4
@Klaim Lihat ini: stackoverflow.com/a/10128180/964135 Sebenarnya itu hanya harus non-throwing, tetapi noexceptjaminan ini.
Pubby
3
"Jika noexceptfungsi melempar maka std::terminatedisebut yang sepertinya akan melibatkan sejumlah kecil overhead" ... Tidak, ini harus diimplementasikan dengan tidak menghasilkan tabel pengecualian untuk fungsi tersebut, yang mana operator pengecualian harus tangkap dan kemudian bail out.
Potatoswatter
8
@Pubby C ++ penanganan pengecualian biasanya dilakukan tanpa overhead kecuali melompat tabel yang memetakan berpotensi melemparkan alamat situs panggilan ke titik masuk handler. Menghapus tabel-tabel itu sedekat mungkin dengan menghapus penanganan eksepsi sepenuhnya. Satu-satunya perbedaan adalah ukuran file yang dapat dieksekusi. Mungkin tidak layak menyebutkan apa pun.
Potatoswatter
26
"Kalau begitu gunakan itu ketika jelas bahwa fungsinya tidak akan pernah melempar." Saya tidak setuju. noexceptadalah bagian dari antarmuka fungsi ; Anda tidak boleh menambahkannya hanya karena implementasi Anda saat ini terjadi untuk tidak melempar. Saya tidak yakin tentang jawaban yang tepat untuk pertanyaan ini, tetapi saya cukup yakin bahwa bagaimana fungsi Anda terjadi pada hari ini tidak ada hubungannya dengan itu ...
Nemo
133

Karena saya terus mengulangi hari-hari ini: semantik yang pertama .

Menambahkan noexcept, noexcept(true)dan noexcept(false)yang pertama dan terutama tentang semantik. Itu hanya secara kebetulan mengkondisikan sejumlah kemungkinan optimasi.

Sebagai seorang programmer yang membaca kode, keberadaannya noexceptmirip dengan const: itu membantu saya lebih baik mengetahui apa yang mungkin atau tidak mungkin terjadi. Karena itu, ada baiknya meluangkan waktu untuk memikirkan apakah Anda tahu atau tidak fungsi akan dilempar. Untuk pengingat, segala jenis alokasi memori dinamis dapat dilemparkan.


Oke, sekarang ke optimasi yang mungkin.

Optimalisasi yang paling jelas sebenarnya dilakukan di perpustakaan. C ++ 11 menyediakan sejumlah sifat yang memungkinkan mengetahui apakah suatu fungsi noexceptatau tidak, dan implementasi Perpustakaan Standar itu sendiri akan menggunakan sifat-sifat tersebut untuk mendukung noexceptoperasi pada objek yang ditentukan pengguna yang mereka manipulasi, jika mungkin. Seperti memindahkan semantik .

Compiler mungkin hanya mencukur sedikit lemak (mungkin) dari pengecualian penanganan data, karena harus memperhitungkan fakta bahwa Anda mungkin telah berbohong. Jika fungsi yang ditandai noexceptmelempar, maka std::terminatedipanggil.

Semantik ini dipilih karena dua alasan:

  • segera mendapat manfaat dari noexceptbahkan ketika dependensi tidak menggunakannya (kompatibilitas ke belakang)
  • memungkinkan spesifikasi noexceptfungsi saat memanggil yang secara teoritis dapat melempar, tetapi tidak diharapkan untuk argumen yang diberikan
Matthieu M.
sumber
2
Mungkin saya naif, tetapi saya akan membayangkan sebuah fungsi yang memanggil noexceptfungsi saja tidak perlu melakukan sesuatu yang istimewa, karena setiap pengecualian yang mungkin muncul memicu terminatesebelum mereka mencapai tingkat ini. Ini sangat berbeda dari harus berurusan dengan dan menyebarkan bad_allocpengecualian.
8
Ya, adalah mungkin untuk mendefinisikan noexcept dengan cara seperti yang Anda sarankan, tetapi itu akan menjadi fitur yang benar-benar tidak dapat digunakan. Banyak fungsi yang dapat dilemparkan jika kondisi tertentu tidak berlaku, dan Anda tidak dapat memanggilnya meskipun Anda tahu kondisinya terpenuhi. Misalnya fungsi apa pun yang dapat membuang std :: invalid_argument.
tr3w
3
@ MatthieuM. Agak terlambat untuk menjawab, namun demikian. Fungsi yang ditandai noexcept dapat memanggil fungsi lain yang dapat melempar, janji adalah bahwa fungsi ini tidak akan memancarkan eksepsi yaitu mereka hanya harus menangani pengecualian sendiri!
Honf
4
Saya telah mengangkat jawaban ini sejak lama, tetapi setelah membaca dan memikirkannya lagi, saya punya komentar / pertanyaan. "Pindahkan semantik" adalah satu - satunya contoh yang pernah saya lihat ada yang memberi di mana noexceptjelas membantu / ide yang bagus. Saya mulai berpikir untuk memindahkan konstruksi, memindahkan tugas, dan bertukar adalah satu-satunya kasus yang ada ... Apakah Anda tahu ada yang lain?
Nemo
5
@Nemo: Di perpustakaan standar, itu mungkin satu-satunya, namun menunjukkan prinsip yang dapat digunakan kembali di tempat lain. Operasi pemindahan adalah operasi yang untuk sementara waktu menempatkan beberapa status dalam "limbo", dan hanya jika noexceptseseorang dapat menggunakannya dengan percaya diri pada sepotong data yang dapat diakses sesudahnya. Saya bisa melihat ide ini digunakan di tempat lain, tetapi pustaka Standar agak tipis dalam C ++ dan hanya digunakan untuk mengoptimalkan salinan elemen yang saya pikir.
Matthieu M.
77

Ini sebenarnya membuat (berpotensi) perbedaan besar bagi pengoptimal dalam kompiler. Compiler benar-benar memiliki fitur ini selama bertahun-tahun melalui pernyataan throw kosong () setelah definisi fungsi, serta ekstensi kepatutan. Saya dapat meyakinkan Anda bahwa kompiler modern memanfaatkan pengetahuan ini untuk menghasilkan kode yang lebih baik.

Hampir setiap optimasi dalam kompiler menggunakan sesuatu yang disebut "flow chart" dari suatu fungsi untuk alasan tentang apa yang legal. Grafik aliran terdiri dari apa yang umumnya disebut "blok" dari fungsi (area kode yang memiliki satu pintu masuk dan satu pintu keluar tunggal) dan tepi antara blok untuk menunjukkan ke mana aliran dapat melompat. Noexcept mengubah grafik aliran.

Anda meminta contoh spesifik. Pertimbangkan kode ini:

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
}

Grafik aliran untuk fungsi ini berbeda jika bardiberi label noexcept(tidak ada cara untuk eksekusi untuk melompat antara akhir bardan pernyataan tangkapan). Ketika diberi label sebagai noexcept, kompiler yakin nilai x adalah 5 selama fungsi baz - blok x = 5 dikatakan "mendominasi" blok baz (x) tanpa tepi dari bar()pernyataan catch.

Kemudian dapat melakukan sesuatu yang disebut "propagasi konstan" untuk menghasilkan kode yang lebih efisien. Di sini jika baz digarisbawahi, pernyataan yang menggunakan x mungkin juga mengandung konstanta dan apa yang dulunya merupakan evaluasi runtime dapat diubah menjadi evaluasi waktu kompilasi, dll.

Pokoknya, jawaban singkat: noexceptmemungkinkan kompiler menghasilkan grafik aliran yang lebih ketat, dan grafik aliran digunakan untuk alasan tentang semua jenis optimasi kompiler umum. Bagi seorang kompiler, penjelasan pengguna dengan sifat ini sangat bagus. Kompiler akan mencoba mencari tahu hal ini, tetapi biasanya tidak bisa (fungsi yang dimaksud mungkin dalam file objek lain tidak terlihat oleh kompiler atau secara transparan menggunakan beberapa fungsi yang tidak terlihat), atau ketika itu terjadi, ada beberapa pengecualian sepele yang mungkin dilemparkan yang bahkan tidak Anda sadari, sehingga tidak dapat secara implisit melabelnya noexcept(mengalokasikan memori dapat melempar bad_alloc, misalnya).

Terry Mahaffey
sumber
3
Apakah ini benar-benar membuat perbedaan dalam praktik? Contoh ini dibikin karena ada sebelum x = 5bisa melempar. Jika bagian dari tryblok itu melayani tujuan apa pun, alasan itu tidak berlaku.
Potatoswatter
7
Saya akan mengatakan itu membuat perbedaan nyata dalam mengoptimalkan fungsi yang mengandung blok try / catch. Contoh yang saya berikan meskipun dibuat-buat, tidak lengkap. Poin yang lebih besar adalah bahwa noexcept (seperti pernyataan throw () sebelum itu) membantu kompilasi menghasilkan grafik aliran yang lebih kecil (lebih sedikit tepi, lebih sedikit blok) yang merupakan bagian mendasar dari banyak optimasi yang dilakukannya.
Terry Mahaffey
Bagaimana kompiler dapat mengenali bahwa kode dapat membuang pengecualian? Apakah dan akses ke array dianggap sebagai pengecualian yang mungkin?
Tomas Kubes
3
@ qub1n Jika kompiler dapat melihat isi dari fungsi, ia dapat mencari throwpernyataan eksplisit , atau hal-hal lain newyang dapat dilemparkan. Jika kompiler tidak dapat melihat tubuh maka harus bergantung pada ada atau tidaknya noexcept. Akses array biasa biasanya tidak menghasilkan pengecualian (C ++ tidak memiliki batas memeriksa) jadi tidak, akses array tidak akan sendirian menyebabkan kompiler berpikir suatu fungsi melempar pengecualian. (Akses tidak terikat adalah UB, bukan pengecualian yang dijamin.)
cdhowie
@cdhowie " itu harus bergantung pada ada atau tidaknya noexcept " atau kehadiran throw()di pre-noexcept C ++
curiousguy
57

noexceptdapat secara dramatis meningkatkan kinerja beberapa operasi. Ini tidak terjadi pada tingkat menghasilkan kode mesin oleh kompiler, tetapi dengan memilih algoritma yang paling efektif: seperti yang lain disebutkan, Anda melakukan pemilihan ini menggunakan fungsi std::move_if_noexcept. Misalnya, pertumbuhan std::vector(misalnya, ketika kita menelepon reserve) harus memberikan jaminan keselamatan-pengecualian yang kuat. Jika ia mengetahui bahwa Tmove constructor tidak melempar, ia hanya dapat memindahkan setiap elemen. Kalau tidak, itu harus menyalin semua Ts. Ini telah dijelaskan secara rinci dalam posting ini .

Andrzej
sumber
4
Tambahan: Itu berarti jika Anda mendefinisikan konstruktor pemindahan atau memindahkan operator penugasan menambahkannya noexcept(jika ada)! Fungsi anggota gerak yang didefinisikan secara implisit telah noexceptditambahkan secara otomatis (jika ada).
mucaho 3-15
33

Kapan saya bisa secara realistis kecuali mengamati peningkatan kinerja setelah menggunakan noexcept? Secara khusus, berikan contoh kode yang kompiler C ++ dapat menghasilkan kode mesin yang lebih baik setelah penambahan noexcept.

Um, tidak pernah? Tidak pernah ada waktu? Tidak pernah.

noexceptadalah untuk optimisasi kinerja kompiler dengan cara yang sama yaitu constuntuk optimisasi kinerja kompiler. Itu hampir tidak pernah.

noexceptterutama digunakan untuk memungkinkan "Anda" untuk mendeteksi pada waktu kompilasi jika suatu fungsi dapat melempar pengecualian. Ingat: kebanyakan kompiler tidak mengeluarkan kode khusus untuk pengecualian kecuali itu benar-benar melempar sesuatu. Jadi noexceptbukan masalah memberikan petunjuk kompiler tentang bagaimana mengoptimalkan suatu fungsi seperti memberi Anda petunjuk tentang cara menggunakan suatu fungsi.

Templat like move_if_noexceptakan mendeteksi jika move constructor didefinisikan dengan noexceptdan akan mengembalikan tipe const&bukan &&jika bukan. Ini adalah cara untuk mengatakan untuk bergerak jika sangat aman untuk melakukannya.

Secara umum, Anda harus menggunakan noexceptketika Anda berpikir itu akan berguna untuk melakukannya. Beberapa kode akan mengambil jalur yang berbeda jika is_nothrow_constructiblebenar untuk jenis itu. Jika Anda menggunakan kode yang akan melakukan itu, jangan ragu untuk noexceptmenggunakan konstruktor yang sesuai.

Singkatnya: gunakan untuk memindahkan konstruktor dan konstruksi serupa, tetapi jangan merasa seperti Anda harus mengacaukannya.

Nicol Bolas
sumber
13
Sebenarnya, move_if_noexcepttidak akan mengembalikan salinan, itu akan mengembalikan referensi nilai konstan daripada referensi nilai. Secara umum itu akan menyebabkan penelepon membuat salinan alih-alih bergerak, tetapi move_if_noexcepttidak melakukan salinan. Kalau tidak, penjelasan yang bagus.
Jonathan Wakely
12
+1 Jonathan. Mengubah ukuran vektor, misalnya, akan memindahkan objek daripada menyalinnya jika konstruktor bergerak noexcept. Sehingga "tidak pernah" itu tidak benar.
mfontanini
4
Maksud saya, kompiler akan menghasilkan kode yang lebih baik dalam situasi itu. OP meminta contoh di mana kompiler dapat menghasilkan aplikasi yang lebih optimal. Ini tampaknya menjadi masalah (walaupun itu bukan optimasi kompiler ).
mfontanini
7
@mfontanini: Compiler hanya menghasilkan kode yang lebih baik karena kompiler dipaksa untuk mengkompilasi codepath yang berbeda . Ini hanya berfungsi karena std::vectorditulis untuk memaksa kompiler untuk mengkompilasi kode yang berbeda . Ini bukan tentang kompiler yang mendeteksi sesuatu; ini tentang kode pengguna mendeteksi sesuatu.
Nicol Bolas
3
Masalahnya adalah, saya tidak bisa menemukan "optimisasi kompiler" dalam kutipan di awal jawaban Anda. Seperti yang dikatakan @ChristianRau, jika kompiler menghasilkan kode yang lebih efisien, tidak masalah apa asal dari optimasi itu. Setelah semua, compiler yang menghasilkan kode yang lebih efisien, bukan? PS: Saya tidak pernah mengatakan itu adalah pengoptimalan kompiler, saya bahkan berkata "Ini bukan pengoptimalan kompiler".
mfontanini
22

Dalam kata-kata Bjarne ( Bahasa Pemrograman C ++, Edisi ke-4 , halaman 366):

Jika penghentian adalah respons yang dapat diterima, pengecualian yang tidak tertangkap akan mencapai hal itu karena itu berubah menjadi panggilan penghentian () (§13.5.2.5). Juga, noexceptspecifier (§13.5.1.1) dapat membuat keinginan itu eksplisit.

Sistem toleran kesalahan yang berhasil adalah bertingkat. Setiap level mengatasi kesalahan sebanyak mungkin tanpa terlalu berkutat dan membiarkan sisanya ke level yang lebih tinggi. Pengecualian mendukung pandangan itu. Lebih jauh lagi, terminate()mendukung pandangan ini dengan memberikan jalan keluar jika mekanisme penanganan pengecualian itu sendiri rusak atau jika telah digunakan secara tidak lengkap, sehingga meninggalkan pengecualian. Demikian pula, noexceptmemberikan jalan keluar sederhana untuk kesalahan di mana mencoba memulihkan tampaknya tidak mungkin dilakukan.

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

Konstruktor vektor mungkin gagal mendapatkan memori untuk sepuluh dobelnya dan melempar a std::bad_alloc. Dalam hal ini, program berakhir. Itu berakhir tanpa syarat dengan memohon std::terminate()(§30.4.1.3). Itu tidak memanggil destruktor dari memanggil fungsi. Ini adalah implementasi yang ditentukan apakah destruktor dari lingkup antara throwdan noexcept(misalnya, untuk s dalam compute ()) dipanggil. Program ini baru saja akan berakhir, jadi kita tidak harus bergantung pada objek apa pun. Dengan menambahkan noexceptspecifier, kami menunjukkan bahwa kode kami tidak ditulis untuk mengatasi lemparan.

Saurav Sahu
sumber
2
Apakah Anda memiliki sumber untuk kutipan ini?
Anton Golov
5
@AntonGolov "Bahasa Pemrograman C ++, Edisi ke-4" hal. 366
Rusty Shackleford
1
Ini terdengar bagi saya seolah-olah saya benar-benar harus menambahkan noexceptsetiap waktu, kecuali saya secara eksplisit ingin mengurus pengecualian. Mari kita menjadi nyata, sebagian besar pengecualian sangat tidak mungkin dan / atau sangat fatal sehingga penyelamatan hampir tidak masuk akal atau mungkin. Misalnya dalam contoh yang dikutip, jika alokasi gagal, aplikasi akan sulit untuk dapat terus bekerja dengan benar.
Neonit
21
  1. Ada banyak contoh fungsi yang saya tahu tidak akan pernah dilempar, tetapi kompiler tidak dapat menentukannya sendiri. Haruskah saya menambahkan noexcept ke deklarasi fungsi dalam semua kasus seperti itu?

noexceptrumit, karena merupakan bagian dari antarmuka fungsi. Terutama, jika Anda menulis perpustakaan, kode klien Anda dapat bergantung pada noexceptproperti. Mungkin sulit untuk mengubahnya nanti, karena Anda mungkin merusak kode yang ada. Itu mungkin tidak terlalu menjadi perhatian ketika Anda menerapkan kode yang hanya digunakan oleh aplikasi Anda.

Jika Anda memiliki fungsi yang tidak dapat dilempar, tanyakan pada diri Anda apakah itu akan tetap noexceptatau apakah itu membatasi implementasi di masa depan? Misalnya, Anda mungkin ingin memperkenalkan pengecekan kesalahan argumen ilegal dengan melemparkan pengecualian (misalnya, untuk pengujian unit), atau Anda mungkin bergantung pada kode perpustakaan lain yang dapat mengubah spesifikasi pengecualiannya. Dalam hal ini, lebih aman bersikap konservatif dan mengabaikan noexcept.

Di sisi lain, jika Anda yakin bahwa fungsi tidak boleh melempar dan itu benar bahwa itu adalah bagian dari spesifikasi, Anda harus mendeklarasikannya noexcept. Namun, perlu diingat bahwa kompiler tidak akan dapat mendeteksi pelanggaran noexceptjika implementasi Anda berubah.

  1. Untuk situasi apa saya harus lebih berhati-hati tentang penggunaan noexcept, dan untuk situasi mana saya bisa lolos dengan noexcept yang tersirat (salah)?

Ada empat kelas fungsi yang harus Anda berkonsentrasi karena mereka akan memiliki dampak terbesar:

  1. memindahkan operasi (memindahkan operator penugasan dan memindahkan konstruktor)
  2. operasi swap
  3. memory deallocators (operator delete, operator delete [])
  4. destruktor (meskipun ini secara implisit noexcept(true)kecuali Anda membuatnya noexcept(false))

Fungsi-fungsi ini umumnya harus noexcept, dan kemungkinan besar implementasi perpustakaan dapat menggunakan noexceptproperti. Misalnya, std::vectordapat menggunakan operasi pemindahan yang tidak melempar tanpa mengorbankan jaminan pengecualian yang kuat. Jika tidak, ia harus kembali ke elemen penyalinan (seperti yang terjadi pada C ++ 98).

Jenis optimasi ini ada pada level algoritmik dan tidak bergantung pada optimisasi kompiler. Ini dapat memiliki dampak yang signifikan, terutama jika elemen-elemennya mahal untuk disalin.

  1. Kapan saya dapat secara realistis berharap untuk mengamati peningkatan kinerja setelah menggunakan noexcept? Secara khusus, berikan contoh kode yang kompiler C ++ dapat menghasilkan kode mesin yang lebih baik setelah penambahan noexcept.

Keuntungan dari noexcepttidak ada spesifikasi pengecualian atau throw()adalah bahwa standar memungkinkan kompiler lebih banyak kebebasan ketika datang ke stack unwinding. Bahkan dalam throw()kasus ini, kompiler harus sepenuhnya melepas tumpukan (dan harus melakukannya dalam urutan terbalik yang tepat dari konstruksi objek).

Dalam hal noexceptini, di sisi lain, tidak perlu melakukan itu. Tidak ada persyaratan bahwa stack harus dibatalkan (tetapi kompiler masih diperbolehkan untuk melakukannya). Kebebasan itu memungkinkan pengoptimalan kode lebih lanjut karena menurunkan overhead untuk selalu dapat melepaskan tumpukan.

Pertanyaan terkait tentang noexcept, stack unwinding, dan kinerja menjelaskan lebih lanjut tentang overhead saat stack unwinding diperlukan.

Saya juga merekomendasikan buku Scott Meyers "Effective Modern C ++", "Item 14: Deklarasikan fungsi-fungsi kecuali jika mereka tidak akan mengeluarkan pengecualian" untuk bacaan lebih lanjut.

Philipp Claßen
sumber
Tetap akan lebih masuk akal jika pengecualian diterapkan di C ++ seperti di Jawa, di mana Anda menandai metode yang mungkin dibuang dengan throwskata kunci dan bukan noexceptnegatif. Saya tidak bisa mendapatkan beberapa pilihan desain C ++ ...
doc
Mereka menamainya noexceptkarena throwsudah diambil. Sederhananya throwdapat digunakan hampir seperti yang Anda sebutkan, kecuali mereka merusak desain itu sehingga menjadi hampir tidak berguna - bahkan merugikan. Tapi kami terjebak dengan itu sekarang karena menghapusnya akan menjadi perubahan besar dengan sedikit manfaat. Jadi noexceptpada dasarnya throw_v2.
AnorZaken
Bagaimana throwtidak bermanfaat?
curiousguy
@curiousguy "throw" sendiri (untuk melempar pengecualian) berguna, tetapi "throw" sebagai specifier pengecualian telah ditinggalkan dan di C ++ 17 bahkan dihapus. Untuk alasan mengapa
penentu
1
@ PhilippClaßen Penentu throw()pengecualian tidak memberikan jaminan yang sama dengan nothrow?
curiousguy
17

Ada banyak contoh fungsi yang saya tahu tidak akan pernah dilempar, tetapi kompiler tidak dapat menentukannya sendiri. Haruskah saya menambahkan noexcept ke deklarasi fungsi dalam semua kasus seperti itu?

Ketika Anda mengatakan "Saya tahu [mereka] tidak akan pernah melempar", maksud Anda dengan memeriksa implementasi fungsi yang Anda tahu bahwa fungsi tersebut tidak akan melempar. Saya pikir pendekatan itu luar dalam.

Lebih baik untuk mempertimbangkan apakah suatu fungsi dapat membuang pengecualian untuk menjadi bagian dari desain fungsi: sama pentingnya dengan daftar argumen dan apakah suatu metode adalah mutator (... const). Menyatakan bahwa "fungsi ini tidak pernah melempar pengecualian" adalah kendala pada implementasi. Menghilangkannya tidak berarti fungsi tersebut dapat memberikan pengecualian; itu berarti bahwa versi fungsi saat ini dan semua versi yang akan datang dapat memberikan pengecualian. Ini adalah kendala yang membuat implementasi lebih sulit. Tetapi beberapa metode harus memiliki batasan agar praktis berguna; yang paling penting, sehingga mereka dapat dipanggil dari destructor, tetapi juga untuk implementasi kode "roll-back" dalam metode yang memberikan jaminan pengecualian yang kuat.

Raedwald
sumber
Ini adalah jawaban terbaik sejauh ini. Anda memberikan jaminan kepada pengguna metode Anda, yang merupakan cara lain untuk mengatakan Anda membatasi implementasi Anda selamanya (tanpa perubahan). Terima kasih atas perspektif yang mencerahkan.
AnorZaken
Lihat juga pertanyaan Java terkait .
Raedwald