Perbedaannya adalah std::make_shared
melakukan satu heap-alokasi, sedangkan memanggil std::shared_ptr
konstruktor melakukan dua.
Di mana alokasi tumpukan terjadi?
std::shared_ptr
mengelola dua entitas:
- blok kontrol (menyimpan data meta seperti penghitungan ulang, penghapusan tipe, dll)
- objek yang dikelola
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 dan std::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,
void F(const std::shared_ptr<Lhs> &lhs, const std::shared_ptr<Rhs> &rhs) { /* ... */ }
F(std::shared_ptr<Lhs>(new Lhs("foo")),
std::shared_ptr<Rhs>(new Rhs("bar")));
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 ke std::shared_ptr
konstruktor.
Salah satu cara untuk memperbaikinya adalah dengan melakukannya pada jalur yang terpisah sehingga pemesanan arbiter ini tidak dapat terjadi.
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
Cara yang lebih disukai untuk menyelesaikan ini tentu saja adalah dengan menggunakannya std::make_shared
.
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
Pembaruan II: Kerugian std::make_shared
Mengutip komentar Casey :
Karena hanya ada satu alokasi, memori orang yang ditunjuk tidak dapat di-deokasi-kan sampai blok kontrol tidak lagi digunakan. A weak_ptr
dapat menjaga blok kontrol tetap hidup tanpa batas.
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. Untuk lock
). Mereka melakukan ini dengan memeriksa jumlah shared_ptr
s yang memiliki objek yang dikelola, yang disimpan di blok kontrol. Hasilnya adalah bahwa blok kontrol masih hidup hingga shared_ptr
hitungan dan weak_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 ada shared_ptr
atau weak_ptr
masih hidup.
Misalkan kita melakukan dua alokasi tumpukan untuk blok kontrol dan objek yang dikelola melalui new
dan shared_ptr
konstruktor. Kemudian kita membebaskan memori untuk objek terkelola (mungkin sebelumnya) ketika tidak ada shared_ptr
yang hidup, dan membebaskan memori untuk blok kontrol (mungkin nanti) ketika tidak ada weak_ptr
yang hidup.