Pointer "mentah" tidak dikelola. Yaitu, baris berikut:
SomeKindOfObject *someKindOfObject = new SomeKindOfObject();
... akan membocorkan memori jika pendamping delete
tidak dilakukan pada waktu yang tepat.
auto_ptr
Untuk meminimalkan kasus ini, std::auto_ptr<>
diperkenalkan. Karena keterbatasan C ++ sebelum standar 2011, bagaimanapun, masih sangat mudah untuk auto_ptr
membocorkan memori. Akan tetapi cukup untuk kasus terbatas, seperti ini:
void func() {
std::auto_ptr<SomeKindOfObject> sKOO_ptr(new SomeKindOfObject());
// do some work
// will not leak if you do not copy sKOO_ptr.
}
Salah satu kasus penggunaan terlemahnya adalah dalam wadah. Ini karena jika salinan yang auto_ptr<>
dibuat dan salinan yang lama tidak diatur ulang dengan hati-hati, maka wadah dapat menghapus pointer dan kehilangan data.
unique_ptr
Sebagai pengganti, C ++ 11 memperkenalkan std::unique_ptr<>
:
void func2() {
std::unique_ptr<SomeKindofObject> sKOO_unique(new SomeKindOfObject());
func3(sKOO_unique); // now func3() owns the pointer and sKOO_unique is no longer valid
}
Seperti unique_ptr<>
akan dibersihkan dengan benar, bahkan ketika itu melewati antara fungsi. Itu melakukan ini dengan secara semantik mewakili "kepemilikan" dari pointer - "pemilik" membersihkannya. Ini membuatnya ideal untuk digunakan dalam wadah:
std::vector<std::unique_ptr<SomeKindofObject>> sKOO_vector();
Berbeda dengan auto_ptr<>
, unique_ptr<>
berperilaku baik di sini, dan ketika vector
mengubah ukuran, tidak ada objek yang akan terhapus secara tidak sengaja saat vector
salinannya menjadi backing store.
shared_ptr
dan weak_ptr
unique_ptr<>
tentu saja berguna, tetapi ada beberapa kasus di mana Anda ingin dua bagian basis kode Anda dapat merujuk ke objek yang sama dan menyalin pointer-nya, sambil tetap dijamin pembersihan yang benar. Misalnya, pohon mungkin terlihat seperti ini, ketika menggunakan std::shared_ptr<>
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Dalam hal ini, kita bahkan dapat berpegang pada banyak salinan dari simpul root, dan pohon akan dibersihkan dengan benar ketika semua salinan dari simpul akar dihancurkan.
Ini berfungsi karena masing-masing shared_ptr<>
memegang tidak hanya pointer ke objek, tetapi juga jumlah referensi dari semua shared_ptr<>
objek yang merujuk ke pointer yang sama. Saat yang baru dibuat, hitungannya naik. Ketika seseorang dihancurkan, hitungannya turun. Saat hitungan mencapai nol, penunjuknya adalah delete
d.
Jadi ini menimbulkan masalah: Struktur yang ditautkan ganda berakhir dengan referensi melingkar. Katakanlah kita ingin menambahkan parent
pointer ke pohon kami Node
:
template<class T>
struct Node {
T value;
std::shared_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Sekarang, jika kita menghapus a Node
, ada referensi siklik untuk itu. Itu tidak akan pernah menjadi delete
d karena jumlah referensi tidak akan pernah menjadi nol.
Untuk mengatasi masalah ini, Anda menggunakan std::weak_ptr<>
:
template<class T>
struct Node {
T value;
std::weak_ptr<Node<T>> parent;
std::shared_ptr<Node<T>> left;
std::shared_ptr<Node<T>> right;
};
Sekarang, semuanya akan berfungsi dengan benar, dan menghapus sebuah simpul tidak akan meninggalkan referensi yang macet ke simpul induk. Itu membuat berjalan pohon sedikit lebih rumit, namun:
std::shared_ptr<Node<T>> parent_of_this = node->parent.lock();
Dengan cara ini, Anda dapat mengunci referensi ke simpul tersebut, dan Anda memiliki jaminan yang masuk akal bahwa simpul itu tidak akan hilang saat Anda mengerjakannya, karena Anda memegangnya shared_ptr<>
.
make_shared
dan make_unique
Sekarang, ada beberapa masalah kecil dengan shared_ptr<>
dan unique_ptr<>
yang harus diatasi. Dua baris berikut memiliki masalah:
foo_unique(std::unique_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
foo_shared(std::shared_ptr<SomeKindofObject>(new SomeKindOfObject()), thrower());
Jika thrower()
melempar pengecualian, kedua saluran akan membocorkan memori. Dan lebih dari itu, shared_ptr<>
menahan jumlah referensi jauh dari objek yang ditunjuknya dan ini bisa berarti alokasi kedua). Itu biasanya tidak diinginkan.
C ++ 11 menyediakan std::make_shared<>()
dan C ++ 14 menyediakan std::make_unique<>()
untuk memecahkan masalah ini:
foo_unique(std::make_unique<SomeKindofObject>(), thrower());
foo_shared(std::make_shared<SomeKindofObject>(), thrower());
Sekarang, dalam kedua kasus, bahkan jika thrower()
melempar pengecualian, tidak akan ada kebocoran memori. Sebagai bonus, make_shared<>()
memiliki kesempatan untuk membuat jumlah referensi dalam ruang memori yang sama dengan objek yang dikelola, yang keduanya dapat lebih cepat dan dapat menghemat beberapa byte memori, sambil memberikan Anda jaminan keamanan pengecualian!
Catatan tentang Qt
Perlu dicatat, bahwa Qt, yang harus mendukung kompiler pra-C ++ 11, memiliki model pengumpulan sampah sendiri: Banyak QObject
yang memiliki mekanisme di mana mereka akan dihancurkan dengan baik tanpa perlu pengguna untuk delete
itu.
Saya tidak tahu bagaimana QObject
s akan berperilaku ketika dikelola oleh pointer yang dikelola C ++ 11, jadi saya tidak bisa mengatakan itu shared_ptr<QDialog>
adalah ide yang bagus. Saya tidak memiliki pengalaman yang cukup dengan Qt untuk mengatakan dengan pasti, tetapi saya percaya bahwa Qt5 telah disesuaikan untuk kasus penggunaan ini.