std::shared_ptr<Object> p1 = std::make_shared<Object>("foo");
std::shared_ptr<Object> p2(new Object("foo"));
Banyak posting google dan stackoverflow ada di sini, tapi saya tidak bisa mengerti mengapa make_shared
lebih efisien daripada langsung menggunakan shared_ptr
.
Dapatkah seseorang menjelaskan kepada saya langkah demi langkah urutan objek yang dibuat dan operasi yang dilakukan oleh keduanya sehingga saya akan dapat memahami bagaimana make_shared
efisien. Saya telah memberikan satu contoh di atas untuk referensi.
c++
c++11
shared-ptr
Anup Buchke
sumber
sumber
make_shared
Anda dapat menulisauto p1(std::make_shared<A>())
dan p1 akan memiliki jenis yang benar.Jawaban:
Perbedaannya adalah
std::make_shared
melakukan satu heap-alokasi, sedangkan memanggilstd::shared_ptr
konstruktor melakukan dua.Di mana alokasi tumpukan terjadi?
std::shared_ptr
mengelola dua entitas:std::make_shared
melakukan akuntansi heap-alokasi tunggal untuk ruang yang diperlukan untuk blok kontrol dan data. Dalam kasus lain,new Obj("foo")
memanggil alokasi tumpukan untuk data yang dikelola danstd::shared_ptr
konstruktor melakukan yang lain untuk blok kontrol.Untuk informasi lebih lanjut, lihat catatan implementasi di cppreference .
Pembaruan I: Pengecualian-Keamanan
CATATAN (2019/08/30) : Ini bukan masalah sejak C ++ 17, karena perubahan dalam urutan evaluasi argumen fungsi. Secara khusus, setiap argumen untuk suatu fungsi diperlukan untuk sepenuhnya dieksekusi sebelum evaluasi argumen lain.
Karena OP tampaknya bertanya-tanya tentang sisi pengecualian-keamanan, saya telah memperbarui jawaban saya.
Pertimbangkan contoh ini,
Karena C ++ memungkinkan urutan evaluasi subekspresi yang sewenang-wenang, satu kemungkinan pemesanan adalah:
new Lhs("foo"))
new Rhs("bar"))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
Sekarang, misalkan kita mendapatkan pengecualian yang dilemparkan pada langkah 2 (mis., Di luar memori,
Rhs
konstruktor melemparkan beberapa pengecualian). Kami kemudian kehilangan memori yang dialokasikan pada langkah 1, karena tidak ada yang memiliki kesempatan untuk membersihkannya. Inti dari masalah di sini adalah bahwa pointer mentah tidak segera diteruskan kestd::shared_ptr
konstruktor.Salah satu cara untuk memperbaikinya adalah dengan melakukannya pada jalur yang terpisah sehingga pemesanan arbiter ini tidak dapat terjadi.
Cara yang lebih disukai untuk menyelesaikan ini tentu saja adalah dengan menggunakannya
std::make_shared
.Pembaruan II: Kerugian
std::make_shared
Mengutip komentar Casey :
Mengapa instance
weak_ptr
s menjaga blok kontrol tetap hidup?Harus ada cara bagi
weak_ptr
s untuk menentukan apakah objek yang dikelola masih valid (mis. Untuklock
). Mereka melakukan ini dengan memeriksa jumlahshared_ptr
s yang memiliki objek yang dikelola, yang disimpan di blok kontrol. Hasilnya adalah bahwa blok kontrol masih hidup hinggashared_ptr
hitungan danweak_ptr
jumlah keduanya mencapai 0.Kembali ke
std::make_shared
Karena
std::make_shared
membuat alokasi heap tunggal untuk blok kontrol dan objek yang dikelola, tidak ada cara untuk membebaskan memori untuk blok kontrol dan objek yang dikelola secara mandiri. Kita harus menunggu sampai kita bisa membebaskan blok kontrol dan objek yang dikelola, yang kebetulan sampai tidak adashared_ptr
atauweak_ptr
masih hidup.Misalkan kita melakukan dua alokasi tumpukan untuk blok kontrol dan objek yang dikelola melalui
new
danshared_ptr
konstruktor. Kemudian kita membebaskan memori untuk objek terkelola (mungkin sebelumnya) ketika tidak adashared_ptr
yang hidup, dan membebaskan memori untuk blok kontrol (mungkin nanti) ketika tidak adaweak_ptr
yang hidup.sumber
make_shared
juga: karena hanya ada satu alokasi, memori pointee tidak dapat dibatalkan alokasinya sampai blok kontrol tidak lagi digunakan. Aweak_ptr
dapat menjaga blok kontrol tetap hidup tanpa batas.make_shared
danmake_unique
secara konsisten, Anda tidak akan memiliki pointer mentah dan dapat memperlakukan setiap kejadiannew
sebagai bau kode.shared_ptr
, dan tidak adaweak_ptr
s, menyerukanreset()
padashared_ptr
contoh akan menghapus blok kontrol. Tapi ini terlepas dari apakahmake_shared
digunakan. Menggunakanmake_shared
membuat perbedaan karena dapat memperpanjang masa pakai memori yang dialokasikan untuk objek yang dikelola . Ketikashared_ptr
hitungan hits 0, destruktor untuk objek yang dikelola dipanggil terlepas darimake_shared
, tetapi membebaskan memori hanya dapat dilakukan jikamake_shared
sedang tidak digunakan. Semoga ini membuatnya lebih jelas.Pointer bersama mengelola objek itu sendiri, dan objek kecil yang berisi jumlah referensi dan data housekeeping lainnya.
make_shared
dapat mengalokasikan satu blok memori untuk menampung keduanya; membangun pointer bersama dari pointer ke objek yang sudah dialokasikan akan perlu mengalokasikan blok kedua untuk menyimpan jumlah referensi.Selain efisiensi ini, menggunakan
make_shared
berarti Anda tidak perlu berurusan dengannew
dan mentah pointer sama sekali, memberikan keamanan pengecualian yang lebih baik - tidak ada kemungkinan melempar pengecualian setelah mengalokasikan objek tetapi sebelum menetapkannya ke smart pointer.sumber
Ada kasus lain di mana dua kemungkinan berbeda, di atas yang sudah disebutkan: jika Anda perlu memanggil konstruktor non-publik (dilindungi atau pribadi), make_share mungkin tidak dapat mengaksesnya, sedangkan varian dengan karya baru baik-baik saja .
sumber
new
, kalau tidak saya akan menggunakanmake_shared
. Berikut adalah pertanyaan terkait tentang itu: stackoverflow.com/questions/8147027/… .Jika Anda memerlukan penyelarasan memori khusus pada objek yang dikendalikan oleh shared_ptr, Anda tidak bisa mengandalkan make_share, tapi saya pikir itu satu-satunya alasan bagus untuk tidak menggunakannya.
sumber
Saya melihat satu masalah dengan std :: make_share, itu tidak mendukung konstruktor pribadi / dilindungi
sumber
Shared_ptr
: Melakukan dua alokasi tumpukanMake_shared
: Melakukan hanya satu alokasi tumpukansumber
Tentang efisiensi dan waktu yang dihabiskan untuk alokasi, saya membuat tes sederhana di bawah ini, saya membuat banyak contoh melalui dua cara ini (satu per satu):
Masalahnya, menggunakan make_share membutuhkan waktu dua kali lipat dibandingkan dengan menggunakan yang baru. Jadi, menggunakan baru ada dua alokasi tumpukan bukannya satu menggunakan make_share. Mungkin ini adalah tes bodoh tapi bukankah itu menunjukkan bahwa menggunakan make_share membutuhkan lebih banyak waktu daripada menggunakan yang baru? Tentu saja, saya hanya berbicara tentang waktu yang digunakan.
sumber
Saya pikir bagian keamanan pengecualian dari jawaban mr mpark masih menjadi perhatian yang valid. saat membuat shared_ptr seperti ini: shared_ptr <T> (T baru), T baru mungkin berhasil, sedangkan alokasi blok kontrol shared_ptr mungkin gagal. dalam skenario ini, T yang baru dialokasikan akan bocor, karena shared_ptr tidak memiliki cara untuk mengetahui bahwa itu dibuat di tempat dan aman untuk menghapusnya. atau aku melewatkan sesuatu? Saya tidak berpikir aturan yang lebih ketat pada evaluasi fungsi parameter membantu dengan cara apa pun di sini ...
sumber