C ++ 17 memperkenalkan [[nodiscard]]
atribut, yang memungkinkan pemrogram menandai fungsi dengan cara yang dikompilasi oleh kompiler jika objek yang dikembalikan dibuang oleh penelepon; atribut yang sama dapat ditambahkan ke seluruh tipe kelas.
Saya telah membaca tentang motivasi untuk fitur ini dalam proposal asli , dan saya tahu bahwa C ++ 20 akan menambahkan atribut ke fungsi standar seperti std::vector::empty
, yang namanya tidak menyampaikan makna yang jelas mengenai nilai pengembalian.
Ini fitur keren dan bermanfaat. Nyatanya, itu sepertinya terlalu berguna. Di mana-mana saya baca [[nodiscard]]
, orang-orang mendiskusikannya seolah-olah Anda hanya akan menambahkannya ke beberapa fungsi atau tipe tertentu dan melupakan sisanya. Tetapi mengapa nilai yang tidak bisa dibuang menjadi kasus khusus, terutama ketika menulis kode baru? Bukankah nilai pengembalian yang dibuang biasanya berupa bug atau setidaknya pemborosan sumber daya?
Dan bukankah salah satu prinsip desain C ++ itu sendiri bahwa kompiler harus menangkap kesalahan sebanyak mungkin?
Jika demikian, mengapa tidak menambahkan [[nodiscard]]
kode non-legacy Anda sendiri ke hampir setiap non- void
fungsi dan hampir setiap jenis kelas tunggal?
Saya sudah mencoba melakukan itu dalam kode saya sendiri, dan itu berfungsi dengan baik, kecuali sangat verbose yang mulai terasa seperti Jawa. Tampaknya jauh lebih alami untuk membuat kompiler memperingatkan tentang nilai pengembalian yang dibuang secara default kecuali untuk beberapa kasus lain di mana Anda menandai niat Anda [*] .
Karena saya telah melihat nol diskusi tentang kemungkinan ini dalam proposal standar, entri blog, pertanyaan Stack Overflow atau di mana pun di internet, saya pasti kehilangan sesuatu.
Mengapa mekanisme seperti itu tidak masuk akal dalam kode C ++ baru? Apakah verbositas satu-satunya alasan untuk tidak menggunakan [[nodiscard]]
hampir di semua tempat?
[*] Secara teori, Anda mungkin memiliki sesuatu seperti [[maydiscard]]
atribut, yang juga dapat ditambahkan secara surut ke fungsi seperti printf
dalam implementasi perpustakaan standar.
operator =
sebagai contoh. Danstd::map::insert
.const
bisa mengasapi sebaliknya "sederhana" kelas (atau lebih tepatnya "biasa lama objek data") jauh di C ++.std::vector
ataustd::unique_ptr
, di mana Anda hanya perlu anggota data dalam definisi kelas Anda. Saya telah bekerja dengan kedua bahasa; Java adalah bahasa yang oke, tetapi lebih verbose.Jawaban:
Dalam kode baru yang tidak perlu kompatibel dengan standar yang lebih lama, gunakan atribut itu di tempat yang masuk akal. Tetapi untuk C ++,
[[nodiscard]]
membuat default yang buruk. Kamu menyarankan:Itu akan tiba-tiba menyebabkan kode yang ada dan benar mengeluarkan banyak peringatan. Sementara perubahan semacam itu secara teknis dapat dianggap kompatibel-mundur karena kode yang ada masih berhasil dikompilasi, itu akan menjadi perubahan besar dalam praktik semantik.
Keputusan desain untuk bahasa dewasa yang sudah ada dengan basis kode yang sangat besar tentu berbeda dengan keputusan desain untuk bahasa yang sama sekali baru. Jika ini adalah bahasa baru, maka peringatan secara default akan masuk akal. Sebagai contoh, bahasa Nim membutuhkan nilai yang tidak dibutuhkan untuk dibuang secara eksplisit - ini akan sama dengan membungkus setiap pernyataan ekspresi dalam C ++ dengan gips
(void)(...)
.Sebuah
[[nodiscard]]
atribut yang paling berguna dalam dua kasus:jika suatu fungsi tidak memiliki efek selain mengembalikan hasil tertentu, yaitu murni. Jika hasilnya tidak digunakan, panggilan itu tentu tidak berguna. Di sisi lain, membuang hasilnya tidak akan salah.
jika nilai kembali harus diperiksa, misalnya untuk antarmuka mirip-C yang mengembalikan kode kesalahan alih-alih melempar. Ini adalah kasus penggunaan utama. Untuk C ++ idiomatis, itu akan sangat langka.
Dua kasus ini meninggalkan ruang besar fungsi tidak murni yang mengembalikan nilai, di mana peringatan seperti itu akan menyesatkan. Sebagai contoh:
Pertimbangkan tipe data antrian dengan
.pop()
metode yang menghapus elemen dan mengembalikan salinan elemen yang dihapus. Metode seperti itu sering kali nyaman. Namun, ada beberapa kasus di mana kami hanya ingin menghapus elemen, tanpa mendapatkan salinan. Itu sangat sah, dan peringatan tidak akan membantu. Desain yang berbeda (sepertistd::vector
) membagi tanggung jawab ini, tetapi memiliki pengorbanan lainnya.Perhatikan bahwa dalam beberapa kasus, salinan harus tetap dibuat sehingga terima kasih kepada RVO untuk mengembalikan salinannya.
Pertimbangkan antarmuka yang lancar, di mana setiap operasi mengembalikan objek sehingga operasi lebih lanjut dapat dilakukan. Dalam C ++, contoh paling umum adalah operator penyisipan arus
<<
. Akan sangat merepotkan untuk menambahkan[[nodiscard]]
atribut ke setiap<<
kelebihan.Contoh-contoh ini menunjukkan bahwa membuat kompilasi kode C ++ idiomatis tanpa peringatan di bawah bahasa "C ++ 17 dengan nodiscard secara default" akan sangat membosankan.
Perhatikan bahwa kode C ++ 17 baru Anda yang mengkilap (tempat Anda dapat menggunakan atribut ini) masih dapat dikompilasi bersama dengan pustaka yang menargetkan standar C ++ yang lebih lama. Kompatibilitas mundur ini sangat penting untuk ekosistem C / C ++. Jadi membuat nodiscard sebagai default akan menghasilkan banyak peringatan untuk kasus penggunaan tipikal, idiomatik - peringatan yang tidak dapat Anda perbaiki tanpa perubahan yang jauh ke kode sumber perpustakaan.
Diperdebatkan, masalahnya di sini bukanlah perubahan semantik tetapi bahwa fitur dari setiap standar C ++ berlaku pada lingkup per-kompilasi-unit dan bukan pada lingkup per-file. Jika / ketika beberapa standar C ++ masa depan menjauh dari file header, perubahan seperti itu akan lebih realistis.
sumber
pop
atauoperator<<
, yang akan ditandai secara eksplisit oleh[[maydiscard]]
atribut imajiner saya , jadi tidak ada peringatan yang dihasilkan. Apakah Anda mengatakan fungsi-fungsi seperti itu pada umumnya jauh lebih umum daripada yang ingin saya percayai, atau jauh lebih umum pada umumnya daripada pada basis kode saya sendiri? Paragraf pertama Anda tentang kode yang ada tentu benar, tetapi masih membuat saya bertanya-tanya apakah ada orang lain yang membumbui semua kode baru mereka[[nodiscard]]
, dan jika tidak, mengapa tidak.returns_an_int() = 0
, jadi mengapa harusreturns_a_str() = ""
bekerja? C ++ 11 menunjukkan ini sebenarnya ide yang buruk, karena sekarang semua kode ini melarang bergerak karena nilainya adalah const. Saya pikir pengambilan yang tepat dari pelajaran itu adalah "itu tidak terserah Anda apa yang dilakukan penelepon Anda dengan hasil Anda". Mengabaikan hasilnya adalah hal yang sangat valid yang mungkin ingin dilakukan oleh penelepon, dan kecuali Anda tahu itu salah (bilah yang sangat tinggi), Anda tidak boleh memblokirnya.empty()
juga masuk akal karena namanya cukup ambigu: apakah itu sebuah predikatis_empty()
atau apakah itu sebuah mutatorclear()
? Anda biasanya tidak akan mengharapkan mutator seperti itu untuk mengembalikan apa pun, sehingga peringatan tentang nilai pengembalian dapat mengingatkan programmer tentang suatu masalah. Dengan nama yang lebih jelas, atribut ini tidak akan seperlunya.[[pure]]
atribut, dan itu harus menyiratkan[[nodiscard]]
. Dengan begitu jelas mengapa hasil ini tidak boleh dibuang. Tapi selain fungsi murni, saya tidak bisa memikirkan kelas fungsi lain yang seharusnya memiliki perilaku ini. Dan sebagian besar fungsi tidak murni, maka saya tidak menganggap nodiscard sebagai default adalah pilihan yang tepat.Alasan saya
[[nodiscard]]
hampir tidak akan kemana-mana:Sekarang, jika Anda menjadikannya default, Anda akan memaksa semua pengembang perpustakaan untuk memaksa semua penggunanya untuk tidak membuang nilai pengembalian. Itu akan mengerikan. Atau Anda memaksa mereka untuk menambahkan
[[maydiscard]]
banyak sekali fungsi, yang juga agak mengerikan.sumber
Sebagai contoh: operator << memiliki nilai balik yang bergantung pada panggilan yang benar-benar diperlukan atau sama sekali tidak berguna. (std :: cout << x << y, yang pertama diperlukan karena mengembalikan aliran, yang kedua tidak berguna sama sekali). Sekarang bandingkan dengan printf di mana semua orang membuang nilai kembali yang ada untuk pengecekan kesalahan, tetapi kemudian operator << tidak memiliki pengecekan kesalahan sehingga tidak mungkin bermanfaat di tempat pertama. Jadi, dalam kedua kasus, nodiscard hanya akan merusak.
sumber
[[nodiscard]]
mana - mana?".