Perbedaannya adalah std::make_sharedmelakukan satu heap-alokasi, sedangkan memanggil std::shared_ptrkonstruktor 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_sharedmelakukan 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_ptrkonstruktor 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, Rhskonstruktor 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_ptrkonstruktor.
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_ptrdapat menjaga blok kontrol tetap hidup tanpa batas.
Mengapa instance weak_ptrs menjaga blok kontrol tetap hidup?
Harus ada cara bagi weak_ptrs untuk menentukan apakah objek yang dikelola masih valid (mis. Untuk lock). Mereka melakukan ini dengan memeriksa jumlah shared_ptrs yang memiliki objek yang dikelola, yang disimpan di blok kontrol. Hasilnya adalah bahwa blok kontrol masih hidup hingga shared_ptrhitungan dan weak_ptrjumlah keduanya mencapai 0.
Kembali ke std::make_shared
Karena std::make_sharedmembuat 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_ptratau weak_ptrmasih hidup.
Misalkan kita melakukan dua alokasi tumpukan untuk blok kontrol dan objek yang dikelola melalui newdan shared_ptrkonstruktor. Kemudian kita membebaskan memori untuk objek terkelola (mungkin sebelumnya) ketika tidak ada shared_ptryang hidup, dan membebaskan memori untuk blok kontrol (mungkin nanti) ketika tidak ada weak_ptryang hidup.