Kata noexcept
kunci 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 noexcept
tampaknya 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
.
Ada banyak contoh fungsi yang saya tahu tidak akan pernah dilempar, tetapi kompiler tidak dapat menentukannya sendiri. Haruskah saya menambahkan
noexcept
deklarasi fungsi dalam semua kasus seperti itu?Harus memikirkan apakah saya perlu menambahkan atau tidak
noexcept
setelah setiap pernyataan fungsi akan sangat mengurangi produktivitas programmer (dan terus terang, akan menyebalkan). Untuk situasi apa saya harus lebih berhati-hati dalam penggunaannyanoexcept
, dan untuk situasi apa saya bisa lolos dari yang tersiratnoexcept(false)
?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 penambahannoexcept
.Secara pribadi, saya peduli
noexcept
karena meningkatnya kebebasan yang diberikan kepada kompiler untuk secara aman menerapkan beberapa jenis optimasi. Apakah kompiler modern mengambil keuntungannoexcept
dengan cara ini? Jika tidak, dapatkah saya mengharapkan beberapa dari mereka melakukannya dalam waktu dekat?
move_if_nothrow
(atau whatchamacallit) akan melihat peningkatan kinerja jika ada ctor langkah noexcept.move_if_noexcept
.Jawaban:
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.
Nah, kemudian gunakan ketika sudah jelas bahwa fungsi tidak akan pernah membuang.
Sepertinya keuntungan pengoptimalan terbesar adalah dari pengoptimalan pengguna, bukan pengompilasi karena kemungkinan memeriksa
noexcept
dan 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
noexcept
di empat besar (konstruktor, tugas, bukan destruktor seperti yang sudah mereka lakukannoexcept
) kemungkinan akan menyebabkan peningkatan terbaik karenanoexcept
cek 'umum' dalam kode templat seperti dalamstd
wadah. Misalnya,std::vector
tidak akan menggunakan gerakan kelas Anda kecuali jika ditandainoexcept
(atau kompilator dapat menyimpulkannya).sumber
std::terminate
triknya masih mematuhi model Zero-Cost. Yaitu, kebetulan bahwa kisaran instruksi dalamnoexcept
fungsi dipetakan untuk memanggilstd::terminate
jikathrow
digunakan sebagai ganti tumpukan yang dibuka. Karena itu saya ragu memiliki lebih banyak overhead daripada pelacakan pengecualian reguler.noexcept
jaminan ini.noexcept
fungsi melempar makastd::terminate
disebut 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.noexcept
adalah 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 ...Karena saya terus mengulangi hari-hari ini: semantik yang pertama .
Menambahkan
noexcept
,noexcept(true)
dannoexcept(false)
yang pertama dan terutama tentang semantik. Itu hanya secara kebetulan mengkondisikan sejumlah kemungkinan optimasi.Sebagai seorang programmer yang membaca kode, keberadaannya
noexcept
mirip denganconst
: 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
noexcept
atau tidak, dan implementasi Perpustakaan Standar itu sendiri akan menggunakan sifat-sifat tersebut untuk mendukungnoexcept
operasi 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
noexcept
melempar, makastd::terminate
dipanggil.Semantik ini dipilih karena dua alasan:
noexcept
bahkan ketika dependensi tidak menggunakannya (kompatibilitas ke belakang)noexcept
fungsi saat memanggil yang secara teoritis dapat melempar, tetapi tidak diharapkan untuk argumen yang diberikansumber
noexcept
fungsi saja tidak perlu melakukan sesuatu yang istimewa, karena setiap pengecualian yang mungkin muncul memicuterminate
sebelum mereka mencapai tingkat ini. Ini sangat berbeda dari harus berurusan dengan dan menyebarkanbad_alloc
pengecualian.noexcept
jelas 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?noexcept
seseorang 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.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:
Grafik aliran untuk fungsi ini berbeda jika
bar
diberi labelnoexcept
(tidak ada cara untuk eksekusi untuk melompat antara akhirbar
dan pernyataan tangkapan). Ketika diberi label sebagainoexcept
, kompiler yakin nilai x adalah 5 selama fungsi baz - blok x = 5 dikatakan "mendominasi" blok baz (x) tanpa tepi daribar()
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:
noexcept
memungkinkan 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 melabelnyanoexcept
(mengalokasikan memori dapat melempar bad_alloc, misalnya).sumber
x = 5
bisa melempar. Jika bagian daritry
blok itu melayani tujuan apa pun, alasan itu tidak berlaku.throw
pernyataan eksplisit , atau hal-hal lainnew
yang dapat dilemparkan. Jika kompiler tidak dapat melihat tubuh maka harus bergantung pada ada atau tidaknyanoexcept
. 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.)throw()
di pre-noexcept C ++noexcept
dapat 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 fungsistd::move_if_noexcept
. Misalnya, pertumbuhanstd::vector
(misalnya, ketika kita meneleponreserve
) harus memberikan jaminan keselamatan-pengecualian yang kuat. Jika ia mengetahui bahwaT
move constructor tidak melempar, ia hanya dapat memindahkan setiap elemen. Kalau tidak, itu harus menyalin semuaT
s. Ini telah dijelaskan secara rinci dalam posting ini .sumber
noexcept
(jika ada)! Fungsi anggota gerak yang didefinisikan secara implisit telahnoexcept
ditambahkan secara otomatis (jika ada).Um, tidak pernah? Tidak pernah ada waktu? Tidak pernah.
noexcept
adalah untuk optimisasi kinerja kompiler dengan cara yang sama yaituconst
untuk optimisasi kinerja kompiler. Itu hampir tidak pernah.noexcept
terutama 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. Jadinoexcept
bukan masalah memberikan petunjuk kompiler tentang bagaimana mengoptimalkan suatu fungsi seperti memberi Anda petunjuk tentang cara menggunakan suatu fungsi.Templat like
move_if_noexcept
akan mendeteksi jika move constructor didefinisikan dengannoexcept
dan akan mengembalikan tipeconst&
bukan&&
jika bukan. Ini adalah cara untuk mengatakan untuk bergerak jika sangat aman untuk melakukannya.Secara umum, Anda harus menggunakan
noexcept
ketika Anda berpikir itu akan berguna untuk melakukannya. Beberapa kode akan mengambil jalur yang berbeda jikais_nothrow_constructible
benar untuk jenis itu. Jika Anda menggunakan kode yang akan melakukan itu, jangan ragu untuknoexcept
menggunakan konstruktor yang sesuai.Singkatnya: gunakan untuk memindahkan konstruktor dan konstruksi serupa, tetapi jangan merasa seperti Anda harus mengacaukannya.
sumber
move_if_noexcept
tidak akan mengembalikan salinan, itu akan mengembalikan referensi nilai konstan daripada referensi nilai. Secara umum itu akan menyebabkan penelepon membuat salinan alih-alih bergerak, tetapimove_if_noexcept
tidak melakukan salinan. Kalau tidak, penjelasan yang bagus.noexcept
. Sehingga "tidak pernah" itu tidak benar.std::vector
ditulis untuk memaksa kompiler untuk mengkompilasi kode yang berbeda . Ini bukan tentang kompiler yang mendeteksi sesuatu; ini tentang kode pengguna mendeteksi sesuatu.Dalam kata-kata Bjarne ( Bahasa Pemrograman C ++, Edisi ke-4 , halaman 366):
sumber
noexcept
setiap 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.noexcept
rumit, karena merupakan bagian dari antarmuka fungsi. Terutama, jika Anda menulis perpustakaan, kode klien Anda dapat bergantung padanoexcept
properti. 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
noexcept
atau 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 mengabaikannoexcept
.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 pelanggarannoexcept
jika implementasi Anda berubah.Ada empat kelas fungsi yang harus Anda berkonsentrasi karena mereka akan memiliki dampak terbesar:
noexcept(true)
kecuali Anda membuatnyanoexcept(false)
)Fungsi-fungsi ini umumnya harus
noexcept
, dan kemungkinan besar implementasi perpustakaan dapat menggunakannoexcept
properti. Misalnya,std::vector
dapat 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.
Keuntungan dari
noexcept
tidak ada spesifikasi pengecualian atauthrow()
adalah bahwa standar memungkinkan kompiler lebih banyak kebebasan ketika datang ke stack unwinding. Bahkan dalamthrow()
kasus ini, kompiler harus sepenuhnya melepas tumpukan (dan harus melakukannya dalam urutan terbalik yang tepat dari konstruksi objek).Dalam hal
noexcept
ini, 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.
sumber
throws
kata kunci dan bukannoexcept
negatif. Saya tidak bisa mendapatkan beberapa pilihan desain C ++ ...noexcept
karenathrow
sudah diambil. Sederhananyathrow
dapat 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. Jadinoexcept
pada dasarnyathrow_v2
.throw
tidak bermanfaat?throw()
pengecualian tidak memberikan jaminan yang sama dengannothrow
?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.sumber