make_unique dan penerusan sempurna

216

Mengapa tidak ada std::make_uniquetemplat fungsi di pustaka C ++ 11 standar? saya menemukan

std::unique_ptr<SomeUserDefinedType> p(new SomeUserDefinedType(1, 2, 3));

sedikit bertele-tele. Bukankah yang berikut ini akan jauh lebih baik?

auto p = std::make_unique<SomeUserDefinedType>(1, 2, 3);

Ini menyembunyikan yang newbaik dan hanya menyebutkan tipe sekali.

Bagaimanapun, ini adalah upaya saya untuk implementasi dari make_unique:

template<typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
    return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

Butuh waktu cukup lama untuk menyelesaikannya std::forward, tapi saya tidak yakin apakah itu benar. Apakah itu? Apa sebenarnya std::forward<Args>(args)...artinya? Apa yang dilakukan oleh kompiler?

fredoverflow
sumber
1
Cukup yakin kami telah melakukan diskusi ini sebelumnya ... juga perhatikan bahwa unique_ptrmengambil parameter templat kedua yang entah bagaimana harus Anda izinkan - yang berbeda dari shared_ptr.
Kerrek SB
5
@ Gerrek: Saya tidak berpikir masuk akal untuk melakukan parameter make_uniquedengan deleter khusus, karena jelas itu mengalokasikan melalui biasa lama newdan karenanya harus menggunakan biasa lama delete:)
fredoverflow
1
@Fred: Itu benar. Jadi, yang diusulkan make_uniqueakan terbatas pada newalokasi ... well, tidak apa-apa jika Anda ingin menulisnya, tapi saya bisa melihat mengapa sesuatu seperti itu bukan bagian dari standar.
Kerrek SB
4
Sebenarnya, saya suka menggunakan make_uniquetemplat karena konstruktor std::unique_ptreksplisit, dan oleh karena itu verbose untuk kembali unique_ptrdari suatu fungsi. Juga, saya lebih suka menggunakan auto p = make_unique<foo>(bar, baz)daripada std::unique_ptr<foo> p(new foo(bar, baz)).
Alexandre C.
5
make_uniqueakan datang C++14, lihat isocpp.org/blog/2013/04/trip-report-iso-c-spring-2013-meeting
stefan

Jawaban:

157

Herb Sutter, ketua komite standardisasi C ++, menulis di blognya :

Bahwa C ++ 11 tidak termasuk make_uniqueadalah sebagian dari pengawasan, dan hampir pasti akan ditambahkan di masa depan.

Dia juga memberikan implementasi yang identik dengan yang diberikan oleh OP.

Sunting: std::make_unique sekarang adalah bagian dari C ++ 14 .

Johan Råde
sumber
2
Sebuah make_uniqueTemplate fungsi sendiri tidak menjamin panggilan aman pengecualian. Itu bergantung pada konvensi, bahwa penelepon menggunakannya. Sebaliknya, pengecekan tipe statis yang ketat (yang merupakan perbedaan utama antara C ++ dan C) dibangun berdasarkan gagasan menegakkan keselamatan, melalui tipe. Dan untuk itu, make_uniquebisa berupa kelas alih-alih fungsi. Misalnya, lihat artikel blog saya dari Mei 2010. Ini juga ditautkan dari diskusi di blog Herb.
Ceria dan hth. - Alf
1
@ DavidRodríguez-dribeas: baca blog Herb untuk memahami masalah keamanan pengecualian. lupakan "tidak dirancang untuk diturunkan", itu hanya sampah. daripada saran saya membuat make_uniquekelas, saya sekarang berpikir lebih baik untuk membuatnya menjadi fungsi yang menghasilkan make_unique_t, alasan untuk itu adalah masalah dengan parse yang paling sesat :-).
Ceria dan hth. - Alf
1
@ Cheersandhth.-Alf: Mungkin kita telah membaca artikel yang berbeda, karena yang baru saja saya baca dengan jelas menyatakan yang make_uniquemenawarkan jaminan pengecualian yang kuat. Atau mungkin Anda sedang mencampur alat dengan penggunaannya, dalam hal ini tidak ada fungsi yang terkecuali aman. Pertimbangkan void f( int *, int* ){}, jelas-jelas menawarkan no throwjaminan, tetapi menurut alasan Anda, itu bukan pengecualian aman, karena dapat disalahgunakan. Lebih buruk lagi, void f( int, int ) {}tidak terkecuali aman juga !: typedef unique_ptr<int> up; f( *up(new int(5)), *up(new int(10)))...
David Rodríguez - dribeas
2
@ Cheersandhth.-Alf: Aku bertanya tentang isu-isu pengecualian di make_unique diimplementasikan seperti di atas dan Anda menunjuk saya untuk sebuah artikel oleh Sutter, artikel yang saya google-fu menunjuk saya untuk menyatakan bahwa make_uniquepenawaran yang jaminan pengecualian yang kuat , yang bertentangan pernyataan Anda di atas. Jika Anda memiliki artikel yang berbeda saya saya tertarik membacanya. Jadi pertanyaan awal saya berdiri Bagaimana make_unique(sebagaimana didefinisikan di atas) tidak terkecuali aman? (Sidenote: ya, saya pikir itu make_unique meningkatkan keamanan pengecualian di tempat-tempat di mana ia dapat diterapkan)
David Rodríguez - dribeas
3
... juga saya tidak mengejar fungsi yang kurang aman untuk digunakan make_unique. Pertama saya tidak melihat bagaimana ini tidak aman, dan saya tidak melihat bagaimana menambahkan tipe tambahan akan membuatnya lebih aman . Apa yang saya tahu adalah bahwa saya tertarik untuk memahami isu-isu yang dapat dimiliki oleh implementasi ini - Saya tidak dapat melihat -, dan bagaimana implementasi alternatif akan menyelesaikannya. Apa konvensi yang make_uniquebergantung pada? Bagaimana Anda menggunakan pengecekan tipe untuk menegakkan keamanan? Itulah dua pertanyaan yang ingin saya jawab.
David Rodríguez - dribeas
78

Bagus, tetapi Stephan T. Lavavej (lebih dikenal sebagai STL) memiliki solusi yang lebih baik make_unique, yang berfungsi dengan benar untuk versi array.

#include <memory>
#include <type_traits>
#include <utility>

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::false_type, Args&&... args) {
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique_helper(std::true_type, Args&&... args) {
   static_assert(std::extent<T>::value == 0,
       "make_unique<T[N]>() is forbidden, please use make_unique<T[]>().");

   typedef typename std::remove_extent<T>::type U;
   return std::unique_ptr<T>(new U[sizeof...(Args)]{std::forward<Args>(args)...});
}

template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args) {
   return make_unique_helper<T>(std::is_array<T>(), std::forward<Args>(args)...);
}

Ini dapat dilihat pada video Core C ++ 6-nya .

Versi terbaru dari versi make_unique STL sekarang tersedia sebagai N3656 . Versi ini diadopsi menjadi konsep C ++ 14.

penyebut
sumber
make_unique diusulkan oleh Stephen T Lavalej ke pembaruan std berikutnya.
tominator
Ini dia suka berbicara tentang menambahkannya. channel9.msdn.com/Series/C9-Lectures-Stephan-T-Lavavej-Core-C-/…
tominator
mengapa membuat semua xeo yang tidak perlu diedit, itu baik-baik saja. Kode itu persis seperti yang ditulis Stephen T. Lavalej, dan dia bekerja untuk dinkumware yang mengelola perpustakaan std. Anda sudah berkomentar di dinding itu, jadi Anda harus tahu.
tominator
3
Implementasi untuk make_uniqueharus masuk dalam tajuk. Header tidak boleh mengimpor namespace (lihat Item # 59 dalam buku "C ++ Coding Standards" Sutter / Alexandrescu). Perubahan Xeo membantu menghindari mendorong praktik buruk.
Bret Kuhns
sayangnya, tidak didukung oleh VC2010, bahkan VC2012 saya pikir, yang keduanya tidak mendukung parameter tempalte
varidic
19

std::make_sharedbukan hanya singkatan untuk std::shared_ptr<Type> ptr(new Type(...));. Itu melakukan sesuatu yang tidak bisa Anda lakukan tanpanya.

Untuk melakukan tugasnya, std::shared_ptrharus mengalokasikan blok pelacak selain menahan penyimpanan untuk penunjuk yang sebenarnya. Namun, karena std::make_sharedmengalokasikan objek yang sebenarnya, ada kemungkinan bahwa std::make_sharedmengalokasikan objek dan blok pelacakan di blok memori yang sama.

Jadi sementara std::shared_ptr<Type> ptr = new Type(...);akan ada dua alokasi memori (satu untuk new, satu di std::shared_ptrblok pelacakan), std::make_shared<Type>(...)akan mengalokasikan satu blok memori.

Itu penting bagi banyak pengguna potensial std::shared_ptr. Satu-satunya hal yang std::make_uniqueakan dilakukan adalah sedikit lebih nyaman. Tidak lebih dari itu.

Nicol Bolas
sumber
2
Itu tidak wajib. Berisik, tetapi tidak diharuskan.
Puppy
3
Ini tidak hanya untuk kenyamanan, tetapi juga meningkatkan keamanan pengecualian dalam kasus tertentu. Lihat jawaban Kerrek SB untuk itu.
Piotr99
19

Sementara tidak ada yang menghentikan Anda dari menulis pembantu Anda sendiri, saya percaya bahwa alasan utama menyediakan make_shared<T>di perpustakaan adalah bahwa itu sebenarnya menciptakan jenis internal yang berbeda dari pointer bersama shared_ptr<T>(new T), yang dialokasikan secara berbeda, dan tidak ada cara untuk mencapai ini tanpa dedikasi pembantu.

make_uniquePembungkus Anda di sisi lain hanyalah gula sintaksis di sekitar newekspresi, jadi meskipun mungkin terlihat menyenangkan bagi mata, itu tidak membawa apa pun newke meja. Koreksi: ini sebenarnya tidak benar: Memiliki panggilan fungsi untuk membungkus newekspresi memberikan keamanan pengecualian, misalnya dalam kasus di mana Anda memanggil suatu fungsi void f(std::unique_ptr<A> &&, std::unique_ptr<B> &&). Memiliki dua mentah newyang tidak dilanjutkan sehubungan dengan satu sama lain berarti bahwa jika satu ekspresi baru gagal dengan pengecualian, yang lain dapat membocorkan sumber daya. Adapun mengapa tidak ada make_uniquedalam standar: Itu baru saja dilupakan. (Ini kadang-kadang terjadi. Tidak ada global std::cbegindalam standar meskipun harus ada satu.)

Perhatikan juga bahwa unique_ptrmengambil parameter templat kedua yang entah bagaimana harus Anda izinkan; ini berbeda dengan shared_ptr, yang menggunakan penghapusan tipe untuk menyimpan delet khusus tanpa menjadikannya bagian dari tipe itu.

Kerrek SB
sumber
1
@ FredOverflow: Pointer bersama adalah kelas yang relatif rumit; secara internal ia menyimpan blok kontrol referensi polimorfik, tetapi ada beberapa jenis blok kontrol. shared_ptr<T>(new T)menggunakan salah satunya, make_shared<T>()menggunakan yang berbeda. Membiarkan itu adalah Good Thing, dan versi make-shared dalam beberapa hal adalah pointer bersama paling ringan yang bisa Anda dapatkan.
Kerrek SB
15
@ FredOverflow: shared_ptrmengalokasikan blok memori dinamis untuk menjaga jumlah dan aksi "disposer" saat Anda membuat a shared_ptr. Jika Anda melewati pointer secara eksplisit, perlu menciptakan "baru" blok, jika Anda menggunakan make_shareditu dapat bundel Anda objek dan data satelit dalam satu blok memori (satu new) yang mengakibatkan lebih cepat alokasi / dealokasi, kurang fragmentasi, dan (biasanya ) perilaku cache yang lebih baik.
Matthieu M.
2
Saya rasa saya perlu menonton kembali shared_ptr dan teman-teman ...
fredoverflow
4
-1 "Pembungkus make_unique Anda di sisi lain hanyalah gula sintaksis di sekitar ekspresi baru, jadi meskipun mungkin terlihat menyenangkan bagi mata, itu tidak membawa sesuatu yang baru ke meja." salah. Itu membawa kemungkinan pengecualian fungsi aman doa. Namun, itu tidak membawa jaminan; untuk itu, perlu kelas sehingga argumen formal dapat dideklarasikan dari kelas itu (Herb menjelaskan bahwa sebagai perbedaan antara opt-in dan opt-out).
Ceria dan hth. - Alf
1
@ Cheersandhth.-Alf: Ya, itu benar. Sejak itu saya menyadari hal ini. Saya akan mengedit jawabannya.
Kerrek SB
13

Dalam C ++ 11 ...digunakan (dalam kode templat) untuk "paket ekspansi" juga.

Syaratnya adalah Anda menggunakannya sebagai akhiran dari ekspresi yang berisi paket parameter yang tidak dikembangkan, dan itu hanya akan menerapkan ekspresi untuk masing-masing elemen paket.

Misalnya, membangun contoh Anda:

std::forward<Args>(args)... -> std::forward<int>(1), std::forward<int>(2),
                                                     std::forward<int>(3)

std::forward<Args...>(args...) -> std::forward<int, int, int>(1,2,3)

Yang terakhir salah saya pikir.

Juga, paket argumen tidak dapat diteruskan ke fungsi yang tidak dikembangkan. Saya tidak yakin tentang paket parameter template.

Matthieu M.
sumber
1
Senang melihat ini dieja. Mengapa tidak menyertakan parameter templat variadic juga?
Kerrek SB
@ Gerrek: karena saya tidak begitu yakin tentang sintaks anehnya, dan saya tidak tahu apakah banyak orang telah bermain dengannya. Jadi saya akan menyimpan apa yang saya tahu. Mungkin diperlukan entri c ++ FAQ, jika seseorang termotivasi dan cukup berpengetahuan, untuk sintaks variadic cukup lengkap.
Matthieu M.
Sintaksnya adalah std::forward<Args>(args)..., yang diperluas ke forward<T1>(x1), forward<T2>(x2), ....
Kerrek SB
1
: Saya pikir forwardsebenarnya selalu membutuhkan parameter template, bukan?
Kerrek SB
1
@ Howard: benar! Memaksakan instantiasi memicu peringatan ( ideone.com/GDNHb )
Matthieu M.
5

Terinspirasi oleh implementasi oleh Stephan T. Lavavej, saya pikir mungkin lebih baik memiliki make_unique yang mendukung perluasan array, ada di github dan saya ingin mendapatkan komentar tentangnya. Ini memungkinkan Anda untuk melakukan ini:

// create unique_ptr to an array of 100 integers
auto a = make_unique<int[100]>();

// create a unique_ptr to an array of 100 integers and
// set the first three elements to 1,2,3
auto b = make_unique<int[100]>(1,2,3); 
Nathan Binkert
sumber