Sejak konstruktor salinan
MyClass(const MyClass&);
dan operator = kelebihan beban
MyClass& operator = (const MyClass&);
memiliki kode yang hampir sama, parameter yang sama, dan hanya berbeda pada bagian pengembalian, apakah mungkin memiliki fungsi yang sama untuk digunakan keduanya?
c++
variable-assignment
copy-constructor
c++-faq
MPelletier
sumber
sumber
Jawaban:
Iya. Ada dua opsi umum. Satu - yang umumnya tidak disarankan - adalah memanggil
operator=
dari konstruktor salinan secara eksplisit:MyClass(const MyClass& other) { operator=(other); }
Namun, menyediakan barang
operator=
merupakan tantangan ketika harus menghadapi keadaan lama dan masalah yang timbul dari penugasan diri. Selain itu, semua anggota dan basis mendapatkan default diinisialisasi terlebih dahulu meskipun mereka akan ditetapkan dariother
. Ini bahkan mungkin tidak berlaku untuk semua anggota dan basis dan bahkan jika valid itu secara semantik berlebihan dan mungkin secara praktis mahal.Solusi yang semakin populer adalah mengimplementasikan
operator=
menggunakan konstruktor salinan dan metode swap.MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
atau bahkan:
MyClass& operator=(MyClass other) { swap(other); return *this; }
SEBUAH
swap
fungsi biasanya mudah ditulis karena hanya menukar kepemilikan internal dan tidak harus membersihkan status yang ada atau mengalokasikan sumber daya baru.Keuntungan dari idiom salin dan swap adalah bahwa ia secara otomatis aman untuk penempatan sendiri dan - asalkan operasi swap tidak ada lemparan - juga sangat aman untuk pengecualian.
Untuk menjadi pengecualian yang aman, operator tugas tertulis 'tangan' biasanya harus mengalokasikan salinan sumber daya baru sebelum mengalokasikan sumber daya lama penerima hak sehingga jika terjadi pengecualian mengalokasikan sumber daya baru, status lama masih dapat dikembalikan ke . Semua ini gratis dengan copy-and-swap tetapi biasanya lebih kompleks, dan karenanya rawan kesalahan, untuk dilakukan dari awal.
Satu hal yang harus diperhatikan adalah memastikan bahwa metode swap adalah swap yang benar, dan bukan default
std::swap
yang menggunakan konstruktor salinan dan operator penugasan itu sendiri.Biasanya
swap
digunakan memberwise .std::swap
berfungsi dan dijamin 'tanpa lemparan' dengan semua tipe dasar dan tipe penunjuk. Sebagian besar petunjuk cerdas juga dapat ditukar dengan jaminan tanpa lemparan.sumber
operator=
dari copy ctor sebenarnya sangat buruk, karena pertama-tama menginisialisasi semua nilai ke beberapa default hanya untuk menimpanya dengan nilai objek lain tepat setelahnya.assign
fungsi anggota yang digunakan oleh copy ctor dan operator penugasan dalam beberapa kasus (untuk kelas ringan). Dalam kasus lain (kasus intensif / penggunaan sumber daya, handle / body) salinan / swap tentu saja adalah cara yang harus dilakukan.Konstruktor salinan melakukan inisialisasi objek yang digunakan untuk menjadi memori mentah untuk pertama kalinya. Operator penugasan, OTOH, menimpa nilai yang ada dengan yang baru. Lebih sering daripada tidak sama sekali, ini melibatkan penghentian sumber daya lama (misalnya, memori) dan mengalokasikan yang baru.
Jika ada kesamaan di antara keduanya, operator penugasan melakukan penghancuran dan konstruksi salinan. Beberapa pengembang dulu benar-benar menerapkan tugas dengan penghancuran di tempat diikuti dengan konstruksi salinan penempatan. Namun, ini ide yang sangat buruk. (Bagaimana jika ini adalah operator penugasan dari kelas dasar yang dipanggil selama penugasan kelas turunan?)
Apa yang biasanya dianggap idiom kanonik saat ini digunakan
swap
seperti yang disarankan Charles:MyClass& operator=(MyClass other) { swap(other); return *this; }
Ini menggunakan konstruksi-salinan (catatan yang
other
disalin) dan kehancuran (itu dihancurkan di akhir fungsi) - dan menggunakannya dalam urutan yang benar, juga: konstruksi (mungkin gagal) sebelum kehancuran (tidak boleh gagal).sumber
swap
diumumkanvirtual
?Sesuatu yang menggangguku tentang:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); swap(tmp); return *this; }
Pertama, membaca kata "swap" ketika pikiran saya memikirkan "salinan" mengganggu akal sehat saya. Juga, saya mempertanyakan tujuan dari trik mewah ini. Ya, pengecualian apa pun dalam membangun sumber daya baru (disalin) harus terjadi sebelum pertukaran, yang tampaknya merupakan cara yang aman untuk memastikan semua data baru diisi sebelum membuatnya ditayangkan.
Tidak apa-apa. Jadi, bagaimana dengan pengecualian yang terjadi setelah penukaran? (ketika sumber daya lama dihancurkan ketika objek sementara keluar dari ruang lingkup) Dari perspektif pengguna tugas, operasi gagal, kecuali tidak. Ini memiliki efek samping yang besar: salinan itu benar-benar terjadi. Hanya beberapa pembersihan sumber daya yang gagal. Keadaan objek tujuan telah diubah meskipun operasi dari luar tampaknya gagal.
Jadi, saya mengusulkan daripada "swap" untuk melakukan "transfer" yang lebih alami:
MyClass& operator=(const MyClass& other) { MyClass tmp(other); transfer(tmp); return *this; }
Masih ada konstruksi objek sementara, tetapi tindakan segera berikutnya adalah membebaskan semua sumber daya tujuan saat ini sebelum memindahkan (dan NULLing sehingga mereka tidak akan membebaskan dua kali) sumber daya dari sumber itu.
Alih-alih {membangun, memindahkan, merusak}, saya mengusulkan {membangun, merusak, memindahkan}. Tindakan, yang merupakan tindakan paling berbahaya, adalah tindakan yang dilakukan terakhir setelah segala sesuatu diselesaikan.
Ya, kerusakan gagal adalah masalah di kedua skema. Data bisa rusak (disalin ketika Anda tidak mengira itu) atau hilang (dibebaskan ketika Anda tidak mengira itu). Hilang lebih baik dari pada rusak. Tidak ada data yang lebih baik dari data buruk.
Transfer, bukan swap. Itu saran saya.
sumber
First, reading the word "swap" when my mind is thinking "copy" irritates
-> Sebagai penulis perpustakaan, Anda biasanya tahu praktik umum (salin + tukar), dan intinya adalahmy mind
. Pikiran Anda sebenarnya tersembunyi di balik antarmuka publik. Itulah semua tentang kode yang dapat digunakan kembali.