shared_ptr magic :)

89

Tuan Lidström dan saya bertengkar :)

Klaim Tn. Lidström adalah bahwa sebuah konstruksi shared_ptr<Base> p(new Derived);tidak memerlukan Base untuk memiliki penghancur virtual:

Armen Tsirunyan : "Benarkah? Akankah shared_ptr membersihkan dengan benar? Bisakah Anda dalam hal ini menunjukkan bagaimana efek tersebut dapat diterapkan?"

Daniel Lidström : "The shared_ptr menggunakan destruktornya sendiri untuk menghapus instance Concrete. Ini dikenal sebagai RAII dalam komunitas C ++. Saran saya adalah Anda mempelajari semua yang Anda bisa tentang RAII. Ini akan membuat pengkodean C ++ Anda jauh lebih mudah saat Anda menggunakan RAII dalam segala situasi. "

Armen Tsirunyan : "Saya tahu tentang RAII, dan saya juga tahu bahwa pada akhirnya destruktor shared_ptr dapat menghapus piksel yang disimpan saat pn mencapai 0. Tetapi jika px memiliki penunjuk tipe statis ke Basedan penunjuk jenis dinamis ke Derived, maka kecuali Basememiliki destruktor virtual, ini akan mengakibatkan perilaku tidak jelas. Koreksi saya jika saya salah. "

Daniel Lidström : "The shared_ptr tahu jenis statisnya adalah Beton. Ia tahu ini sejak saya meluluskannya di konstruktornya! Sepertinya agak ajaib, tapi saya jamin itu sesuai desain dan sangat bagus."

Jadi, nilai kami. Bagaimana mungkin (jika ada) untuk mengimplementasikan shared_ptr tanpa memerlukan kelas polimorfik untuk memiliki destruktor virtual? Terima kasih sebelumnya

Armen Tsirunyan
sumber
3
Anda bisa saja menautkan ke utas asli .
Darin Dimitrov
8
Hal menarik lainnya adalah itu shared_ptr<void> p(new Derived)juga akan menghancurkan Derivedobjek oleh destruktornya, terlepas dari apakah itu benar virtualatau tidak.
dalle
7
Cara yang mengagumkan untuk mengajukan pertanyaan :)
rubenvb
5
Meskipun shared_ptr memungkinkan ini, itu ide yang sangat buruk untuk mendesain kelas sebagai basis tanpa dtor virtual. Komentar Daniel tentang RAII menyesatkan — tidak ada hubungannya dengan ini — tetapi percakapan yang dikutip terdengar seperti miskomunikasi sederhana (dan asumsi yang salah tentang cara kerja shared_ptr).
6
Bukan RAII, melainkan jenis-menghapus destruktor. Anda harus berhati-hati, karena di shared_ptr<T>( (T*)new U() )mana struct U:Ttidak akan melakukan hal yang benar (dan ini dapat dilakukan secara tidak langsung dengan mudah, seperti fungsi yang mengambil T*dan dilewatkan a U*)
Yakk - Adam Nevraumont

Jawaban:

74

Ya, itu mungkin untuk mengimplementasikan shared_ptr seperti itu. Boost tidak dan standar C ++ 11 juga memerlukan perilaku ini. Sebagai fleksibilitas tambahan, shared_ptr mengelola lebih dari sekadar penghitung referensi. Penghapus yang disebut biasanya dimasukkan ke dalam blok memori yang sama yang juga berisi penghitung referensi. Tetapi bagian yang menyenangkan adalah tipe dari deleter ini bukan bagian dari tipe shared_ptr. Ini disebut "type erasure" dan pada dasarnya teknik yang sama digunakan untuk mengimplementasikan "fungsi polimorfik" boost :: function atau std :: function untuk menyembunyikan tipe functor yang sebenarnya. Untuk membuat contoh Anda berfungsi, kita membutuhkan konstruktor template:

template<class T>
class shared_ptr
{
public:
   ...
   template<class Y>
   explicit shared_ptr(Y* p);
   ...
};

Jadi, jika Anda menggunakan ini dengan kelas Anda Base dan Derived ...

class Base {};
class Derived : public Base {};

int main() {
   shared_ptr<Base> sp (new Derived);
}

... konstruktor templated dengan Y = Derived digunakan untuk membangun objek shared_ptr. Dengan demikian, konstruktor memiliki kesempatan untuk membuat objek penghapus dan penghitung referensi yang sesuai dan menyimpan penunjuk ke blok kontrol ini sebagai anggota data. Jika penghitung referensi mencapai nol, deleter yang diketahui sebelumnya dan Derived-aware akan digunakan untuk membuang objek.

Standar C ++ 11 mengatakan hal berikut tentang konstruktor ini (20.7.2.2.1):

Membutuhkan: p harus dapat dikonversi ke T*. Yakan menjadi tipe yang lengkap. Ekspresi delete pharus dalam bentuk yang baik, perilaku yang terdefinisi dengan baik, dan tidak boleh memberikan pengecualian.

Efek: Membangun shared_ptrobjek yang memiliki penunjuk p.

Dan untuk destruktor (20.7.2.2.2):

Efek: Jika *thisadalah kosong atau kepemilikan saham dengan yang lain shared_ptrmisalnya ( use_count() > 1), tidak ada efek samping. Jika tidak, jika *thismemiliki objek pdan deleter d, d(p)dipanggil. Sebaliknya, jika *thismemiliki pointer p, dan delete pdipanggil.

(penekanan menggunakan huruf tebal adalah milik saya).

menjual
sumber
the upcoming standard also requires this behaviour: (a) Standar yang mana dan (b) dapatkah Anda memberikan referensi (untuk standar)?
kevinarpe
Saya hanya ingin menambahkan komentar ke jawaban @sellibitze karena saya tidak punya cukup poin add a comment. IMO, itu lebih Boost does thisdari the Standard requires. Saya tidak berpikir Standar membutuhkan itu dari apa yang saya pahami. Berbicara tentang contoh @sellibitze 's shared_ptr<Base> sp (new Derived);, Memerlukan dari constructorhanya meminta delete Derivedmenjadi didefinisikan dengan baik dan terbentuk dengan baik. Untuk spesifikasinya destructorjuga ada a p, tapi saya rasa tidak mengacu ppada spesifikasi yang ada constructor.
Lujun Weng
28

Ketika shared_ptr dibuat, ia menyimpan objek deleter di dalamnya. Objek ini dipanggil saat shared_ptr akan membebaskan resource yang diarahkan. Karena Anda tahu cara menghancurkan sumber daya pada titik konstruksi, Anda dapat menggunakan shared_ptr dengan tipe yang tidak lengkap. Siapa pun yang membuat shared_ptr menyimpan penghapusan yang benar di sana.

Misalnya, Anda dapat membuat penghapus khusus:

void DeleteDerived(Derived* d) { delete d; } // EDIT: no conversion needed.

shared_ptr<Base> p(new Derived, DeleteDerived);

p akan memanggil DeleteDerived untuk menghancurkan objek yang dituju. Implementasinya melakukan ini secara otomatis.

Yakov Galka
sumber
4
+1 untuk komentar tentang tipe yang tidak lengkap, sangat berguna saat menggunakan sebuah shared_ptrsebagai atribut.
Matthieu M.
16

Secara sederhana,

shared_ptr menggunakan fungsi deleter khusus yang dibuat oleh konstruktor yang selalu menggunakan destruktor dari objek yang diberikan dan bukan destruktor dari Base, ini sedikit berfungsi dengan pemrograman meta template, tetapi berfungsi.

Sesuatu seperti itu

template<typename SomeType>
shared_ptr(SomeType *p)
{
   this->destroyer = destroyer_function<SomeType>(p);
   ...
}
Artyom
sumber
1
hmm ... menarik, saya mulai percaya ini :)
Armen Tsirunyan
1
@Armen Tsirunyan Anda harus mengintip deskripsi desain shared_ptr sebelum memulai diskusi. 'Tangkapan dari penghapus' ini adalah salah satu fitur penting dari shared_ptr ...
Paul Michalik
6
@ paul_71: Saya setuju dengan Anda. Di sisi lain saya percaya diskusi ini bermanfaat tidak hanya untuk saya, tetapi juga untuk orang lain yang tidak mengetahui fakta tentang shared_ptr. Jadi saya rasa bukanlah dosa besar untuk memulai utas ini :)
Armen Tsirunyan
3
@Armen Tentu saja tidak. Sebaliknya, Anda melakukan pekerjaan yang baik dengan menunjuk ke fitur yang sangat sangat penting dari shared_ptr <T> yang sering diawasi bahkan oleh pengembang c ++ berpengalaman.
Paul Michalik