- Apa artinya menyalin objek ?
- Apa konstruktor salin dan operator penugasan salinan ?
- Kapan saya harus menyatakannya sendiri?
- Bagaimana saya bisa mencegah objek saya disalin?
c++
copy-constructor
assignment-operator
c++-faq
rule-of-three
fredoverflow
sumber
sumber
c++-faq
tag wiki sebelum Anda memilih untuk menutup .Jawaban:
pengantar
C ++ memperlakukan variabel tipe yang ditentukan pengguna dengan semantik nilai . Ini berarti bahwa objek secara tersirat disalin dalam berbagai konteks, dan kita harus memahami apa sebenarnya arti "menyalin objek".
Mari kita perhatikan contoh sederhana:
(Jika Anda bingung dengan
name(name), age(age)
bagian ini, ini disebut daftar penginisialisasi anggota .)Fungsi anggota khusus
Apa artinya menyalin
person
objek? Themain
Fungsi menunjukkan dua skenario menyalin berbeda. Inisialisasiperson b(a);
dilakukan oleh copy constructor . Tugasnya adalah membangun objek baru berdasarkan keadaan objek yang ada. Penugasanb = a
dilakukan oleh operator penugasan salinan . Pekerjaannya umumnya sedikit lebih rumit, karena objek target sudah dalam keadaan valid yang perlu ditangani.Karena kami mendeklarasikan bukan pembuat salinan atau operator penugasan (atau destruktor) sendiri, ini secara implisit didefinisikan untuk kami. Kutipan dari standar:
Secara default, menyalin objek berarti menyalin anggotanya:
Definisi tersirat
Fungsi anggota khusus yang didefinisikan secara implisit untuk
person
terlihat seperti ini:Penyalinan dengan anggota adalah yang kita inginkan dalam kasus ini:
name
danage
disalin, jadi kita mendapatkanperson
objek mandiri yang mandiri . Destructor yang didefinisikan secara implisit selalu kosong. Ini juga baik dalam hal ini karena kami tidak memperoleh sumber daya apa pun di konstruktor. Destructor anggota secara implisit dipanggil setelahperson
destruktor selesai:Mengelola sumber daya
Jadi kapan kita harus mendeklarasikan fungsi anggota khusus itu secara eksplisit? Ketika kelas kita mengelola sumber daya , yaitu, ketika sebuah objek kelas bertanggung jawab atas sumber daya itu. Itu biasanya berarti sumber daya diperoleh dalam konstruktor (atau diteruskan ke konstruktor) dan dirilis pada destruktor.
Mari kita kembali ke masa pra-standar C ++. Tidak ada yang namanya
std::string
, dan programmer suka dengan pointer. Theperson
kelas mungkin tampak seperti ini:Bahkan hari ini, orang masih menulis kelas dengan gaya ini dan mendapat masalah: " Saya mendorong seseorang ke vektor dan sekarang saya mendapatkan kesalahan memori gila! " Ingat bahwa secara default, menyalin objek berarti menyalin anggota, tetapi menyalin
name
anggota hanya menyalin sebuah pointer, bukan array karakter yang ditunjuknya! Ini memiliki beberapa efek yang tidak menyenangkan:a
dapat diamati melaluib
.b
dihancurkan,a.name
adalah pointer menjuntai.a
dihancurkan, menghapus pointer menggantung menghasilkan perilaku yang tidak terdefinisi .name
menunjuk sebelum tugas, cepat atau lambat Anda akan mendapatkan kebocoran memori di semua tempat.Definisi eksplisit
Karena penyalinan dengan anggota tidak memiliki efek yang diinginkan, kita harus mendefinisikan konstruktor salin dan operator penugasan secara eksplisit untuk membuat salinan mendalam dari susunan karakter:
Perhatikan perbedaan antara inisialisasi dan penugasan: kita harus merobohkan keadaan lama sebelum menugaskan
name
untuk mencegah kebocoran memori. Juga, kita harus melindungi diri dari penugasan formulirx = x
. Tanpa centang itu,delete[] name
akan menghapus array yang berisi string sumber , karena ketika Anda menulisx = x
, keduanyathis->name
danthat.name
berisi pointer yang sama.Keamanan pengecualian
Sayangnya, solusi ini akan gagal jika
new char[...]
melempar pengecualian karena kehabisan memori. Salah satu solusi yang mungkin adalah dengan memperkenalkan variabel lokal dan menyusun ulang pernyataan:Ini juga menangani penugasan diri tanpa pemeriksaan eksplisit. Solusi yang lebih kuat untuk masalah ini adalah idiom copy-and-swap , tapi saya tidak akan membahas detail keamanan pengecualian di sini. Saya hanya menyebutkan pengecualian untuk membuat poin berikut: Menulis kelas yang mengelola sumber daya sulit.
Sumber daya yang tidak dapat didokumentasikan
Beberapa sumber daya tidak dapat atau tidak boleh disalin, seperti pegangan file atau mutex. Dalam hal itu, cukup deklarasikan copy constructor dan copy assignment operator
private
tanpa memberikan definisi:Atau, Anda dapat mewarisi
boost::noncopyable
atau mendeklarasikannya sebagai dihapus (dalam C ++ 11 dan di atas):Aturan tiga
Terkadang Anda perlu mengimplementasikan kelas yang mengelola sumber daya. (Jangan pernah mengelola banyak sumber daya dalam satu kelas, ini hanya akan menimbulkan rasa sakit.) Dalam hal ini, ingat aturan tiga :
(Sayangnya, "aturan" ini tidak ditegakkan oleh standar C ++ atau kompiler apa pun yang saya ketahui.)
Aturan lima
Dari C ++ 11 dan seterusnya, sebuah objek memiliki 2 fungsi anggota khusus ekstra: konstruktor pemindahan dan pemindahan tugas. Aturan lima negara untuk mengimplementasikan fungsi-fungsi ini juga.
Contoh dengan tanda tangan:
Aturan nol
Aturan 3/5 juga disebut sebagai aturan 0/3/5. Bagian nol dari aturan menyatakan bahwa Anda diizinkan untuk tidak menulis fungsi anggota khusus saat membuat kelas Anda.
Nasihat
Sebagian besar waktu, Anda tidak perlu mengelola sumber daya sendiri, karena kelas yang ada seperti
std::string
sudah melakukannya untuk Anda. Bandingkan saja kode sederhana menggunakanstd::string
anggota dengan alternatif berbelit-belit dan rawan menggunakanchar*
dan Anda harus diyakinkan. Selama Anda tinggal jauh dari anggota pointer mentah, aturan tiga kemungkinan tidak akan menyangkut kode Anda sendiri.sumber
The Rule of Three adalah aturan praktis untuk C ++, pada dasarnya mengatakan
Alasan untuk ini adalah bahwa ketiganya biasanya digunakan untuk mengelola sumber daya, dan jika kelas Anda mengelola sumber daya, biasanya perlu mengelola penyalinan serta membebaskan.
Jika tidak ada semantik yang baik untuk menyalin sumber daya yang dikelola kelas Anda, maka pertimbangkan untuk melarang menyalin dengan mendeklarasikan (tidak mendefinisikan ) operator penyalin dan penugasan sebagai
private
.(Perhatikan bahwa versi baru standar C ++ yang akan datang (yang merupakan C ++ 11) menambahkan pindahan semantik ke C ++, yang kemungkinan akan mengubah Aturan Tiga. Namun, saya tahu terlalu sedikit tentang ini untuk menulis bagian C ++ 11 tentang Aturan Tiga.)
sumber
boost::noncopyable
). Itu juga bisa menjadi lebih jelas. Saya berpikir bahwa C ++ 0x dan kemungkinan untuk "menghapus" fungsi dapat membantu di sini, tetapi lupa sintaksnya: /noncopyable
adalah bagian dari std lib, saya tidak menganggapnya sebagai perbaikan. (Oh, dan jika Anda lupa sintaks penghapusan, Anda lupa mor ethan yang pernah saya kenal.:)
)Hukum tiga besar adalah sebagaimana ditentukan di atas.
Contoh mudah, dalam bahasa Inggris sederhana, dari jenis masalah yang dipecahkannya:
Destruktor non default
Anda mengalokasikan memori di konstruktor Anda dan karenanya Anda perlu menulis destruktor untuk menghapusnya. Kalau tidak, Anda akan menyebabkan kebocoran memori.
Anda mungkin berpikir bahwa ini adalah pekerjaan yang dilakukan.
Masalahnya adalah, jika salinan dibuat dari objek Anda, maka salinan itu akan menunjuk ke memori yang sama dengan objek aslinya.
Sekali, salah satu dari ini menghapus memori dalam destruktornya, yang lain akan memiliki pointer ke memori yang tidak valid (ini disebut pointer menggantung) ketika mencoba menggunakannya hal-hal yang akan menjadi berbulu.
Oleh karena itu, Anda menulis copy constructor sehingga mengalokasikan objek baru yang akan dihancurkan oleh memori mereka sendiri.
Operator penugasan dan copy constructor
Anda mengalokasikan memori di konstruktor Anda ke pointer anggota kelas Anda. Saat Anda menyalin objek kelas ini, operator penugasan default dan copy constructor akan menyalin nilai dari pointer anggota ini ke objek baru.
Ini berarti bahwa objek baru dan objek lama akan menunjuk pada bagian memori yang sama sehingga ketika Anda mengubahnya di satu objek itu akan berubah untuk objek lain juga. Jika satu objek menghapus memori ini, yang lain akan melanjutkan mencoba menggunakannya - eek.
Untuk mengatasinya, Anda menulis versi konstruktor dan tugas penugasan versi Anda sendiri. Versi Anda mengalokasikan memori terpisah ke objek-objek baru dan menyalin nilai-nilai yang ditunjuk oleh pointer pertama daripada alamatnya.
sumber
Pada dasarnya jika Anda memiliki destruktor (bukan destruktor default) itu berarti kelas yang Anda tentukan memiliki alokasi memori. Misalkan kelas digunakan di luar oleh beberapa kode klien atau oleh Anda.
Jika MyClass hanya memiliki beberapa anggota yang diketik primitif operator penugasan default akan bekerja tetapi jika memiliki beberapa anggota penunjuk dan objek yang tidak memiliki operator penugasan, hasilnya akan tidak dapat diprediksi. Oleh karena itu kita dapat mengatakan bahwa jika ada sesuatu untuk dihapus pada destruktor suatu kelas, kita mungkin memerlukan operator penyalinan yang dalam yang berarti kita harus menyediakan operator penyalin dan penugasan salinan.
sumber
Apa artinya menyalin objek? Ada beberapa cara Anda dapat menyalin objek - mari kita bicara tentang 2 jenis yang paling Anda rujuk - salinan dalam dan salinan dangkal.
Karena kita menggunakan bahasa berorientasi objek (atau setidaknya mengasumsikan demikian), katakanlah Anda memiliki memori yang dialokasikan. Karena ini adalah bahasa OO, kita dapat dengan mudah merujuk pada potongan memori yang kita alokasikan karena mereka biasanya variabel primitif (ints, karakter, byte) atau kelas yang kita definisikan terbuat dari tipe dan primitif kita sendiri. Jadi katakanlah kita memiliki kelas Mobil sebagai berikut:
Salinan yang dalam adalah jika kita mendeklarasikan objek dan kemudian membuat salinan objek yang sepenuhnya terpisah ... kita berakhir dengan 2 objek dalam 2 set memori yang sepenuhnya.
Sekarang mari kita lakukan sesuatu yang aneh. Katakanlah car2 diprogram dengan salah atau dengan sengaja dimaksudkan untuk membagikan memori aktual yang dibuat dari car1. (Biasanya kesalahan untuk melakukan ini dan di kelas biasanya selimut itu dibahas di bawah.) Berpura-pura bahwa setiap kali Anda bertanya tentang car2, Anda benar-benar menyelesaikan pointer ke ruang memori car1 ... itu kurang lebih seperti salinan yang dangkal adalah.
Jadi, terlepas dari bahasa apa yang Anda tulis, berhati-hatilah dengan apa yang Anda maksud ketika menyalin objek karena sebagian besar waktu Anda menginginkan salinan yang dalam.
Apa konstruktor salin dan operator penugasan salinan? Saya sudah menggunakannya di atas. Copy constructor dipanggil ketika Anda mengetik kode seperti pada
Car car2 = car1;
dasarnya jika Anda mendeklarasikan variabel dan menetapkannya dalam satu baris, saat itulah copy constructor dipanggil. Operator penugasan adalah apa yang terjadi ketika Anda menggunakan tanda sama dengan--car2 = car1;
. Pemberitahuancar2
tidak dinyatakan dalam pernyataan yang sama. Dua potongan kode yang Anda tulis untuk operasi ini kemungkinan sangat mirip. Sebenarnya pola desain yang khas memiliki fungsi lain yang Anda panggil untuk mengatur semuanya setelah Anda puas dengan salinan / penugasan awal yang sah - jika Anda melihat kode lama yang saya tulis, fungsinya hampir identik.Kapan saya harus menyatakannya sendiri? Jika Anda tidak menulis kode yang akan dibagikan atau untuk produksi dengan cara tertentu, Anda benar-benar hanya perlu menyatakannya saat Anda membutuhkannya. Anda harus mengetahui apa yang dilakukan bahasa program Anda jika Anda memilih untuk menggunakannya 'secara tidak sengaja' dan tidak membuatnya - yaitu Anda mendapatkan default kompiler. Saya jarang menggunakan copy constructor misalnya, tetapi menimpa operator penugasan sangat umum. Tahukah Anda bahwa Anda dapat mengesampingkan apa arti penambahan, pengurangan, dll. Juga?
Bagaimana saya bisa mencegah objek saya disalin? Mengganti semua cara Anda diizinkan mengalokasikan memori untuk objek Anda dengan fungsi pribadi adalah awal yang masuk akal. Jika Anda benar-benar tidak ingin orang menyalinnya, Anda dapat membuatnya publik dan mengingatkan programmer dengan melemparkan pengecualian dan juga tidak menyalin objek.
sumber
Aturan Tiga menyatakan bahwa jika Anda mendeklarasikan salah satu dari a
maka Anda harus mendeklarasikan ketiganya. Tumbuhnya dari pengamatan bahwa kebutuhan untuk mengambil alih arti dari operasi penyalinan hampir selalu berasal dari kelas yang melakukan semacam manajemen sumber daya, dan yang hampir selalu menyiratkan bahwa
manajemen sumber daya apa pun yang sedang dilakukan dalam satu operasi penyalinan mungkin perlu dilakukan dalam operasi penyalinan lainnya dan
destruktor kelas juga akan berpartisipasi dalam pengelolaan sumber daya (biasanya melepaskannya). Sumber daya klasik yang akan dikelola adalah memori, dan inilah sebabnya semua kelas Perpustakaan Standar yang mengelola memori (misalnya, wadah STL yang melakukan manajemen memori dinamis) semuanya menyatakan "tiga besar": operasi penyalinan dan destruktor.
Konsekuensi dari Aturan Tiga adalah bahwa keberadaan destruktor yang dideklarasikan oleh pengguna menunjukkan bahwa salinan bijaksana anggota sederhana tidak mungkin sesuai untuk operasi penyalinan di kelas. Itu, pada gilirannya, menunjukkan bahwa jika suatu kelas menyatakan destruktor, operasi penyalinan mungkin tidak boleh dibuat secara otomatis, karena mereka tidak akan melakukan hal yang benar. Pada saat C ++ 98 diadopsi, signifikansi dari garis penalaran ini tidak sepenuhnya dihargai, sehingga dalam C ++ 98, keberadaan pengguna yang dinyatakan destruktor tidak berdampak pada kesediaan kompiler untuk menghasilkan operasi penyalinan. Itu terus menjadi kasus di C ++ 11, tetapi hanya karena membatasi kondisi di mana operasi penyalinan dihasilkan akan memecah terlalu banyak kode warisan.
Menyatakan operator konstruktor & salin penyalinan sebagai penentu akses pribadi.
Di C ++ 11 dan seterusnya, Anda juga dapat mendeklarasikan copy constructor & operator penugasan dihapus
sumber
Banyak jawaban yang ada sudah menyentuh copy constructor, operator penugasan, dan destructor. Namun, dalam posting C ++ 11, pengenalan langkah semantik dapat memperluas ini di luar 3.
Baru-baru ini Michael Claisse memberikan ceramah yang menyentuh topik ini: http://channel9.msdn.com/events/CPP/C-PP-Con-2014/The-Canonical-Class
sumber
Aturan tiga dalam C ++ adalah prinsip dasar desain dan pengembangan tiga persyaratan bahwa jika ada definisi yang jelas dalam salah satu fungsi anggota berikut, maka programmer harus mendefinisikan dua fungsi anggota lainnya secara bersamaan. Yaitu, tiga fungsi anggota berikut sangat diperlukan: destruktor, copy constructor, operator penugasan salinan.
Salin konstruktor di C ++ adalah konstruktor khusus. Ini digunakan untuk membangun objek baru, yang merupakan objek baru yang setara dengan salinan objek yang sudah ada.
Operator penugasan salin adalah operator penugasan khusus yang biasanya digunakan untuk menentukan objek yang ada untuk orang lain dari jenis objek yang sama.
Ada beberapa contoh cepat:
sumber