Mengapa menggunakan std :: make_unique di C ++ 17?

96

Sejauh yang saya mengerti, C ++ 14 diperkenalkan std::make_uniquekarena, sebagai hasil dari urutan evaluasi parameter tidak ditentukan, ini tidak aman:

f(std::unique_ptr<MyClass>(new MyClass(param)), g()); // Syntax A

(Penjelasan: jika evaluasi pertama-tama mengalokasikan memori untuk pointer mentah, kemudian panggilan g()dan pengecualian dilemparkan sebelum std::unique_ptrkonstruksi, maka memori tersebut bocor.)

Menelepon std::make_uniqueadalah cara untuk membatasi urutan panggilan, sehingga membuat semuanya aman:

f(std::make_unique<MyClass>(param), g());             // Syntax B

Sejak itu, C ++ 17 telah mengklarifikasi urutan evaluasi, membuat Sintaks A aman juga, jadi inilah pertanyaan saya: apakah masih ada alasan untuk menggunakan konstruktor std::make_uniqueover std::unique_ptrdi C ++ 17? Bisakah Anda memberikan beberapa contoh?

Sampai sekarang, satu-satunya alasan yang dapat saya bayangkan adalah bahwa ini memungkinkan untuk mengetik MyClasshanya sekali (dengan asumsi Anda tidak perlu bergantung pada polimorfisme dengan std::unique_ptr<Base>(new Derived(param))). Namun, itu sepertinya alasan yang cukup lemah, terutama ketika std::make_uniquetidak memungkinkan untuk menentukan penghapus sementara std::unique_ptrkonstruktor melakukannya.

Dan hanya untuk memperjelas, saya tidak menganjurkan untuk menghapus std::make_uniquedari Perpustakaan Standar (menjaganya setidaknya masuk akal untuk kompatibilitas ke belakang), tetapi bertanya-tanya apakah masih ada situasi di mana itu sangat disukai untukstd::unique_ptr

Kekal
sumber
4
Namun, itu sepertinya alasan yang cukup lemah -> Mengapa itu alasan yang lemah? Ini secara efektif mengurangi duplikasi kode jenis. Sedangkan untuk deleter, seberapa sering Anda menggunakan custom deleter saat Anda menggunakannya std::unique_ptr? Ini bukan argumen untuk dilawanmake_unique
llllllllll
2
Saya mengatakan itu alasan yang lemah karena jika tidak ada std::make_uniquedi tempat pertama, saya tidak berpikir itu akan menjadi alasan yang cukup untuk menambahkannya ke STL, terutama ketika itu adalah sintaks yang kurang ekspresif daripada menggunakan konstruktor, tidak lebih
Eternal
1
Jika Anda memiliki program, dibuat di c ++ 14, menggunakan make_unique, Anda tidak ingin fungsinya dihapus dari stl. Atau jika Anda ingin kompatibel ke belakang.
Serge
2
@Serge Itu poin yang bagus, tapi itu sedikit di samping objek pertanyaan saya. Saya akan mengedit untuk membuatnya lebih jelas
Eternal
1
@Eternal tolong berhenti merujuk ke C ++ Pustaka Standar sebagai STL karena itu salah dan membuat kebingungan. Lihat stackoverflow.com/questions/5205491/…
Marandil

Jawaban:

74

Anda benar bahwa alasan utama dihapus. Masih ada yang tidak menggunakan pedoman baru dan alasan pengetikan yang kurang (tidak harus mengulangi jenis atau menggunakan kata new). Memang itu bukan argumen yang kuat tetapi saya sangat suka tidak melihat newdi kode saya.

Juga jangan lupa tentang konsistensi. Anda benar-benar harus menggunakannya make_sharedsehingga penggunaan make_uniqueitu alami dan sesuai dengan pola. Maka sepele untuk mengubah std::make_unique<MyClass>(param)ke std::make_shared<MyClass>(param)(atau sebaliknya) di mana sintaks A membutuhkan lebih banyak penulisan ulang.

NathanOliver
sumber
41
@reggaeguitar Jika saya melihat newsaya perlu berhenti dan berpikir: berapa lama pointer ini akan hidup? Apakah saya menanganinya dengan benar? Jika ada pengecualian, apakah semuanya sudah dibersihkan dengan benar? Saya tidak ingin bertanya pada diri sendiri pertanyaan-pertanyaan itu dan membuang waktu saya untuk itu dan jika saya tidak menggunakannya new, saya tidak perlu menanyakan pertanyaan-pertanyaan itu.
NathanOliver
5
Bayangkan Anda melakukan grep pada semua file sumber proyek Anda dan tidak menemukan satu pun new. Bukankah ini luar biasa?
Sebastian Mach
5
Keuntungan utama dari pedoman "jangan gunakan baru" adalah karena sederhana, jadi ini adalah pedoman yang mudah untuk diberikan kepada pengembang yang kurang berpengalaman yang mungkin bekerja sama dengan Anda. Saya tidak menyadarinya pada awalnya, tetapi itu memiliki nilai dalam dan dari dirinya sendiri
Eternal
@NathanOliver Anda sebenarnya tidak mutlak harus menggunakan std::make_shared- bayangkan kasus di mana objek yang dialokasikan besar dan ada banyak std::weak_ptr-s yang menunjuk ke sana: akan lebih baik membiarkan objek tersebut dihapus segera setelah terakhir dibagikan pointer dihancurkan dan hidup hanya dengan area bersama yang kecil.
Dev Null
1
@NathanOver Anda tidak akan. Apa yang saya bicarakan adalah kerugian dari std::make_shared stackoverflow.com/a/20895705/8414561 di mana memori yang digunakan untuk menyimpan objek tidak dapat dibebaskan sampai yang terakhir std::weak_ptrhilang (bahkan jika semua std::shared_ptrmenunjuk ke sana (dan akibatnya objek itu sendiri) telah dihancurkan).
Dev Null
50

make_uniquemembedakan Tdari T[]dan T[N], unique_ptr(new ...)tidak.

Anda bisa dengan mudah mendapatkan perilaku tak terdefinisi dengan meneruskan penunjuk yang new[]diedit ke a unique_ptr<T>, atau dengan meneruskan penunjuk yang newdiedit ke unique_ptr<T[]>.

Caleth
sumber
Ini lebih buruk: Tidak hanya tidak, itu benar-benar tidak bisa.
Deduplicator
22

Alasannya adalah untuk memiliki kode yang lebih pendek tanpa duplikat. Membandingkan

f(std::unique_ptr<MyClass>(new MyClass(param)), g());
f(std::make_unique<MyClass>(param), g());

Anda menghemat MyClass, newdan kawat gigi. Biayanya hanya satu karakter yang lebih dalam make dibandingkan dengan ptr .

SM
sumber
2
Nah, seperti yang saya katakan dalam pertanyaan, saya bisa melihat itu lebih sedikit mengetik dengan hanya satu penyebutan MyClass, tetapi saya bertanya-tanya apakah ada alasan yang lebih kuat untuk menggunakannya
Eternal
2
Dalam banyak kasus, panduan deduksi akan membantu menghilangkan <MyClass>bagian dalam varian pertama.
AnT
9
Itu sudah dikatakan di komentar untuk jawaban lain, tetapi sementara c ++ 17 memperkenalkan pengurangan jenis template untuk konstruktor, dalam kasus std::unique_ptritu tidak diizinkan. Ini ada hubungannya dengan membedakan std::unique_ptr<T>danstd::unique_ptr<T[]>
Eternal
19

Setiap penggunaan newharus diaudit secara ekstra hati-hati untuk kebenaran seumur hidup; apakah itu terhapus? Hanya sekali?

Setiap penggunaan make_uniquetidak untuk karakteristik ekstra tersebut; selama objek yang memiliki memiliki masa hidup "benar", itu secara rekursif membuat penunjuk unik memiliki "benar".

Sekarang, benar bahwa unique_ptr<Foo>(new Foo())identik dalam segala hal 1 dengan make_unique<Foo>(); itu hanya membutuhkan "grep kode sumber Anda untuk semua penggunaan newuntuk mengauditnya" yang lebih sederhana.


1 sebenarnya kebohongan dalam kasus umum. Penerusan sempurna tidaklah sempurna {}, default init, array adalah pengecualian.

Yakk - Adam Nevraumont
sumber
Secara teknis unique_ptr<Foo>(new Foo)tidak cukup identik dengan make_unique<Foo>()... yang terakhir melakukannya new Foo()Tapi sebaliknya, ya.
Barry
@barry true, operator baru yang kelebihan beban dimungkinkan.
Yakk - Adam Nevraumont
@dedup apa itu sihir C ++ 17?
Yakk - Adam Nevraumont
2
@Deduplicator sementara c ++ 17 memperkenalkan pengurangan jenis template untuk konstruktor, dalam kasus std::unique_ptritu tidak diizinkan. Jika ada hubungannya dengan membedakan std::unique_ptr<T>danstd::unique_ptr<T[]>
Eternal
@ Yakk-AdamNevraumont Saya tidak bermaksud membebani yang baru, yang saya maksud hanyalah default-init vs value-init.
Barry
0

Sejak itu, C ++ 17 telah mengklarifikasi urutan evaluasi, membuat sintaks A aman juga

Itu tidak cukup baik. Mengandalkan klausul teknis yang baru-baru ini diperkenalkan sebagai jaminan keselamatan bukanlah praktik yang sangat kuat:

  • Seseorang mungkin mengkompilasi kode ini di C ++ 14.
  • Anda akan mendorong penggunaan raw newdi tempat lain, misalnya dengan copy-paste contoh Anda.
  • Seperti yang disarankan SM, karena ada duplikasi kode, satu jenis mungkin berubah tanpa yang lain diubah.
  • Beberapa jenis pemfaktoran ulang IDE otomatis mungkin memindahkannya ke newtempat lain (ok, memang, tidak banyak peluang untuk itu).

Secara umum, sebaiknya kode Anda sesuai / kuat / valid dengan jelas tanpa menggunakan tata bahasa, mencari klausa teknis yang kecil atau tidak jelas dalam standar.

(Ini pada dasarnya adalah argumen yang sama yang saya buat di sini tentang urutan penghancuran tupel.)

einpoklum
sumber
-1

Pertimbangkan fungsi void (std :: unique_ptr (new A ()), std :: unique_ptr (new B ())) {...}

Misalkan A () baru berhasil, tetapi B baru () memunculkan pengecualian: Anda menangkapnya untuk melanjutkan eksekusi normal program Anda. Sayangnya, standar C ++ tidak mengharuskan objek A dihancurkan dan memorinya dibatalkan alokasinya: memori bocor secara diam-diam dan tidak ada cara untuk membersihkannya. Dengan membungkus A dan B ke dalam std :: make_uniques Anda yakin kebocoran tidak akan terjadi:

void function (std :: make_unique (), std :: make_unique ()) {...} Intinya di sini adalah bahwa std :: make_unique dan std :: make_unique sekarang menjadi objek sementara, dan pembersihan objek sementara ditentukan dengan benar di standar C ++: penghancurnya akan dipicu dan memori dibebaskan. Jadi jika Anda bisa, selalu pilih untuk mengalokasikan objek menggunakan std :: make_unique dan std :: make_shared.

Dhanya Gopinath
sumber
4
Penulis secara eksplisit menentukan pertanyaan bahwa dalam C ++ 17 kebocoran tidak akan terjadi lagi. "Sejak itu, C ++ 17 telah mengklarifikasi urutan evaluasi, membuat Sintaks A aman juga, jadi inilah pertanyaan saya: (...)". Anda tidak menjawab pertanyaannya.
R2RT