Idiom C ++ manakah yang tidak digunakan lagi dalam C ++ 11?

192

Dengan standar baru, ada cara-cara baru dalam melakukan sesuatu, dan banyak yang lebih baik daripada cara-cara lama, tetapi cara lama masih baik-baik saja. Juga jelas bahwa standar baru tidak secara resmi mencela, karena alasan kompatibilitas. Jadi pertanyaan yang tersisa adalah:

Apa cara pengkodean lama yang jelas lebih rendah dari gaya C ++ 11, dan apa yang bisa kita lakukan sekarang?

Dalam menjawab ini, Anda dapat melewati hal-hal yang jelas seperti "gunakan variabel otomatis".

Alan Baljeu
sumber
13
Anda tidak dapat mencela idiom.
Pubby
6
Pembicaraan Herb Sutter di Going Native 2012 meliput ini:
bames53
5
Mengembalikan nilai konstan tidak lagi dianjurkan. Jelas auto_ptrsudah usang juga.
Kerrek SB
27
Tentu saja bisa, Pubby. Sebelum templat C ++ ditemukan, ada teknik makro untuk melakukan templat. Kemudian C ++ menambahkannya, dan cara lama dianggap buruk.
Alan Baljeu
7
Pertanyaan ini benar-benar perlu dipindahkan ke Programmers.se.
Nicol Bolas

Jawaban:

173
  1. Kelas Final : C ++ 11 menyediakan finalspecifier untuk mencegah penurunan kelas
  2. C ++ 11 lambdas secara substansial mengurangi kebutuhan akan kelas function object (functor) bernama.
  3. Move Constructor : Cara magis di mana std::auto_ptrpekerjaan tidak lagi diperlukan karena dukungan kelas satu untuk referensi nilai.
  4. Safe bool : Ini disebutkan sebelumnya. Operator eksplisit C ++ 11 meniadakan idiom C ++ 03 yang sangat umum ini.
  5. Shrink-to-fit : Banyak wadah C ++ 11 STL menyediakan ashrink_to_fit() fungsi anggota, yang seharusnya menghilangkan kebutuhan pertukaran dengan sementara.
  6. Temporary Base Class : Beberapa pustaka C ++ lama menggunakan idiom yang agak rumit ini. Dengan memindahkan semantik tidak lagi diperlukan.
  7. Ketik Enum Enumerasi Aman di C ++ 11.
  8. Melarang alokasi heap : = deleteSintaksnya adalah cara yang jauh lebih langsung untuk mengatakan bahwa fungsi tertentu secara eksplisit ditolak. Ini berlaku untuk mencegah alokasi tumpukan (yaitu, =deleteuntuk anggota operator new), mencegah salinan, penugasan, dll.
  9. Templated typedef : Alias ​​templates di C ++ 11 mengurangi kebutuhan untuk typedef templated sederhana. Namun, generator tipe kompleks masih membutuhkan fungsi meta.
  10. Beberapa perhitungan waktu kompilasi numerik, seperti Fibonacci dapat dengan mudah diganti menggunakan ekspresi konstan umum
  11. result_of: Penggunaan templat kelas result_ofharus diganti dengan decltype. Saya pikir result_ofmenggunakan decltypeketika tersedia.
  12. Inisialisasi anggota di dalam kelas menyimpan pengetikan untuk inisialisasi default anggota non-statis dengan nilai default.
  13. Dalam kode C ++ 11 yang baru NULLharus didefinisikan ulang sebagai nullptr, tetapi lihat pembicaraan STL untuk mengetahui mengapa mereka memutuskan untuk tidak melakukannya.
  14. Fanatik template ekspresi senang memiliki sintaks fungsi tipe trailing return di C ++ 11. Tidak ada lagi jenis pengembalian panjang 30-baris!

Saya pikir saya akan berhenti di sana!

Sumant
sumber
Terima kasih untuk detailnya!
Alan Baljeu
7
Jawaban yang bagus, tetapi saya akan mencoret result_ofdari daftar. Meskipun typenamediperlukan rumit sebelumnya, saya pikir typename result_of<F(Args...)::typekadang-kadang bisa lebih mudah dibaca daripada decltype(std::declval<F>()(std::declval<Args>()...), dan dengan penerimaan N3436 ke dalam kertas kerja mereka berdua bekerja untuk SFINAE (yang dulunya merupakan keuntungan dari decltypeyang result_oftidak menawarkan)
Jonathan Wakely
Mengenai 14) Saya masih menangis bahwa saya harus menggunakan makro untuk menulis kode yang sama dua kali - satu kali untuk fungsi tubuh dan satu kali untuk pernyataan decltype () ...
2
Saya ingin mencatat bahwa topik ini ditautkan dari halaman Microsoft ini sebagai artikel "Untuk informasi lebih lanjut" dalam pengantar umum untuk bahasa C ++, tetapi topik ini sangat khusus! Bolehkah saya menyarankan bahwa singkat "Topik ini BUKAN untuk pemula C ++!" saran dimasukkan di awal topik atau jawaban ini?
Aacini
Re 12: "Inisialisasi anggota di dalam kelas" - itu adalah idiom baru, bukan idiom yang sudah usang, bukan? Ganti urutan kalimat mungkin? Re 2: Functors sangat berguna ketika Anda ingin membagikan tipe daripada objek (terutama dalam parameter templat). Jadi itu hanya beberapa penggunaan fungsi yang sudah usang.
einpoklum
66

Pada satu titik waktu dikatakan bahwa seseorang harus kembali dengan constnilai alih-alih hanya dengan nilai:

const A foo();
^^^^^

Ini sebagian besar tidak berbahaya di C ++ 98/03, dan mungkin bahkan menangkap beberapa bug yang terlihat seperti:

foo() = a;

Tapi kembali oleh constdikontraindikasikan dalam C ++ 11 karena menghambat semantik bergerak:

A a = foo();  // foo will copy into a instead of move into it

Jadi santai saja dan kode:

A foo();  // return by non-const value
Howard Hinnant
sumber
9
Namun kesalahan yang dapat dicegah sekarang dapat ditangkap dengan menggunakan kualifikasi referensi untuk fungsi. Seperti dalam kasus di atas, A& operator=(A o)&bukan mendefinisikan A& operator=(A o). Ini mencegah kesalahan konyol dan membuat kelas berperilaku lebih seperti tipe dasar dan tidak mencegah semantik bergerak.
Joe
61

Segera setelah Anda dapat meninggalkan 0dan NULLmendukung nullptr, lakukanlah!

Dalam kode non-generik, penggunaan 0atau NULLbukan merupakan masalah besar. Tetapi segera setelah Anda mulai melewati konstanta penunjuk nol dalam kode generik situasinya berubah dengan cepat. Ketika Anda lulus 0ke template<class T> func(T) Takan dideduksi sebagai intdan bukan sebagai pointer nol konstan. Dan itu tidak dapat dikonversi kembali ke konstanta penunjuk nol setelah itu. Ini mengalir ke rawa-rawa masalah yang sama sekali tidak ada jika alam semesta hanya digunakan nullptr.

C ++ 11 tidak usang 0dan NULLsebagai konstanta penunjuk nol. Tetapi Anda harus kode seolah-olah itu.

Howard Hinnant
sumber
apa itu decltype (nullptr)?
4
@ GrapschKnutsch: Ya std::nullptr_t.
Howard Hinnant
Sarankan ini diulangi sebagai idiom yang sudah usang daripada konvensi baru untuk diadopsi (misalnya "Penggunaan 0atau NULLuntuk null pointer").
einpoklum
38

Idioma bool amanexplicit operator bool().

Pembuat salinan pribadi (boost :: noncopyable) → X(const X&) = delete

Mensimulasikan kelas terakhir dengan destruktor pribadi dan warisan virtualclass X final

kennytm
sumber
contoh yang baik dan ringkas, salah satunya bahkan membawa kata "idiom" di dalamnya. baik menempatkan
Sebastian Mach
2
Wow, saya belum pernah melihat 'idiom bool aman' sebelumnya, terlihat sangat menjijikkan! Saya harap saya tidak pernah membutuhkannya dalam kode pra-C ++ 11 ...
boycy
24

Salah satu hal yang hanya membuat Anda menghindari penulisan algoritma dasar dalam C ++ 11 adalah ketersediaan lambdas dalam kombinasi dengan algoritma yang disediakan oleh perpustakaan standar.

Saya menggunakan itu sekarang dan itu luar biasa seberapa sering Anda hanya mengatakan apa yang ingin Anda lakukan dengan menggunakan count_if (), for_each () atau algoritma lain daripada harus menulis loop sialan lagi.

Setelah Anda menggunakan kompiler C ++ 11 dengan pustaka standar C ++ 11 yang lengkap, Anda tidak punya alasan lagi untuk tidak menggunakan algoritma standar untuk membangun . Lambda baru saja membunuhnya.

Mengapa?

Dalam prakteknya (setelah menggunakan cara penulisan algoritma ini sendiri) rasanya jauh lebih mudah untuk membaca sesuatu yang dibangun dengan kata-kata langsung yang berarti apa yang dilakukan daripada dengan beberapa loop yang harus Anda enkripsi untuk mengetahui artinya. Yang mengatakan, membuat argumen lambda secara otomatis menyimpulkan akan banyak membantu membuat sintaks lebih mudah dibandingkan dengan loop mentah.

Pada dasarnya, algoritma pembacaan yang dibuat dengan algoritma standar jauh lebih mudah karena kata-kata menyembunyikan detail implementasi loop.

Saya menduga hanya algoritma level yang lebih tinggi yang harus dipikirkan sekarang karena kita memiliki algoritma level yang lebih rendah untuk dibangun.

Klaim
sumber
8
Sebenarnya ada alasan bagus. Anda menggunakan algoritme Boost.Range , yang jauh lebih bagus;)
Nicol Bolas
10
Saya tidak melihat bahwa for_eachdengan lambda lebih baik daripada rentang yang setara untuk loop, dengan isi lambda di loop. Kode ini terlihat kurang lebih sama, tetapi lambda memperkenalkan beberapa tanda baca tambahan. Anda dapat menggunakan hal-hal yang setara seperti boost::irangemenerapkannya ke lebih banyak loop daripada hanya yang menggunakan iterator. Plus range-range untuk loop memiliki fleksibilitas yang lebih besar, di mana Anda dapat keluar lebih awal jika diperlukan (oleh returnatau oleh break), sedangkan dengan for_eachAnda harus membuang.
Steve Jessop
5
@SteveJessop: Meski begitu, ketersediaan berbasis jangkauan formembuat it = c.begin(), const end = c.end(); it != end; ++itidiom yang biasa mati.
Ben Voigt
7
@SteveJessop Salah satu keuntungan dari for_eachalgoritma pada rentang berdasarkan loop adalah bahwa Anda tidak bisa break atau return. Yaitu, ketika Anda melihat for_eachAnda segera tahu tanpa melihat tubuh yang tidak ada trickiness tersebut.
bames53
5
@Klaim: lebih spesifik, saya membandingkan misalnya std::for_each(v.begin(), v.end(), [](int &i) { ++i; });dengan for (auto &i : v) { ++i; }. Saya menerima bahwa fleksibilitas bermata dua ( gotosangat fleksibel, itulah masalahnya). Saya tidak berpikir bahwa kendala tidak mampu menggunakan breakdalam for_eachmengkompensasi versi untuk bertele-tele ekstra itu menuntut - pengguna dari for_eachsini IMO mengorbankan mudah dibaca dan kenyamanan yang sebenarnya untuk jenis gagasan teoritis bahwa for_eachini pada prinsipnya lebih jelas dan konseptual lebih sederhana. Dalam praktiknya itu tidak jelas atau sederhana.
Steve Jessop
10

Anda harus menerapkan versi khusus yang swaplebih jarang. Dalam C ++ 03, non-throwing yang efisien swapsering diperlukan untuk menghindari mahal dan membuang salinan, dan karena std::swapmenggunakan dua salinan, swapsering kali harus disesuaikan. Dalam C ++, std::swappenggunaan move, dan fokusnya bergeser pada penerapan konstruktor pemindahan yang efisien dan non-lempar dan memindahkan penugasan Karena untuk ini default sering kali baik-baik saja, ini akan jauh lebih sedikit bekerja daripada di C ++ 03.

Secara umum sulit untuk memprediksi idiom mana yang akan digunakan karena mereka diciptakan melalui pengalaman. Kita dapat mengharapkan "Efektif C ++ 11" mungkin tahun depan, dan "Standar C ++ 11 Coding" hanya dalam tiga tahun karena pengalaman yang diperlukan belum ada di sana.

Philipp
sumber
1
Saya ragu akan hal ini. Gaya yang disarankan adalah menggunakan swap untuk memindahkan dan menyalin konstruksi, tetapi tidak std :: swap karena itu akan melingkar.
Alan Baljeu
Ya tetapi move constructor biasanya memanggil swap kustom, atau itu pada dasarnya setara.
Balik
2

Saya tidak tahu nama untuk itu, tetapi kode C ++ 03 sering menggunakan konstruk berikut sebagai pengganti untuk tugas pindah yang hilang:

std::map<Big, Bigger> createBigMap(); // returns by value

void example ()
{
  std::map<Big, Bigger> map;

  // ... some code using map

  createBigMap().swap(map);  // cheap swap
}

Ini menghindari penyalinan karena salin salin digabungkan dengan di swapatas.

Andrzej
sumber
1
Dalam contoh Anda, swap tidak perlu, salin salinan akan membangun nilai kembali dengan cara apa pun map. Teknik yang Anda tampilkan bermanfaat jika mapsudah ada, bukan hanya sedang dibangun. Contohnya akan lebih baik tanpa komentar "konstruktor default murah" dan dengan "// ..." antara konstruksi dan swap
Jonathan Wakely
Saya mengubahnya sesuai saran Anda. Terima kasih.
Andrzej
Penggunaan "besar" dan "Lebih besar" membingungkan. Mengapa tidak menjelaskan bagaimana ukuran kunci dan tipe nilai penting?
einpoklum
1

Ketika saya perhatikan bahwa kompiler yang menggunakan standar C ++ 11 tidak lagi kesalahan kode berikut:

std::vector<std::vector<int>> a;

untuk operator yang seharusnya berisi >>, saya mulai menari. Dalam versi sebelumnya yang harus dilakukan

std::vector<std::vector<int> > a;

Lebih buruk lagi, jika Anda harus men-debug ini, Anda tahu betapa mengerikannya pesan kesalahan yang keluar dari ini.

Namun, saya tidak tahu apakah ini "jelas" bagi Anda.

v010dya
sumber
1
Fitur ini sudah ditambahkan di C ++ sebelumnya. Atau setidaknya Visual C ++ menerapkannya per diskusi standar bertahun-tahun sebelumnya.
Alan Baljeu
1
@AlanBaljeu Tentu saja, ada banyak hal non-standar yang ditambahkan ke kompiler / perpustakaan. Ada banyak kompiler yang memiliki deklarasi variabel "otomatis" sebelum C ++ 11, tetapi Anda tidak dapat memastikan bahwa kode Anda benar-benar dapat dikompilasi oleh hal lain. Pertanyaannya adalah tentang standar, bukan tentang "apakah ada kompiler yang bisa melakukan ini".
v010dya
1

Pengembalian dengan nilai tidak lagi menjadi masalah. Dengan memindahkan semantik dan / atau optimalisasi nilai kembali (bergantung pada kompiler), fungsi pengkodean lebih alami tanpa overhead atau biaya (sebagian besar waktu).

Martin A
sumber
... tapi idiom mana yang sudah usang?
einpoklum
Bukan idiom tapi itu praktik yang baik tidak diperlukan lagi. Bahkan dengan kompiler didukung RVO yang bersifat opsional. en.wikipedia.org/wiki/Return_value_optimization "Pada tahap awal evolusi C ++, ketidakmampuan bahasa untuk secara efisien mengembalikan objek tipe kelas dari suatu fungsi dianggap sebagai kelemahan ....." struct Data {char bytes [ 16]; }; membatalkan f (Data * p) {// hasil langsung di * p} int main () {Data d; f (& d); }
Martin A
Saya mengisyaratkan Anda harus mengatakan jawaban Anda sebagai "kebiasaan menghindari pengembalian dengan nilai tidak lagi relevan seperti dll. Dll."
einpoklum