Saya baru-baru ini telah membaca tentang memindahkan konstruktor di C ++ (lihat misalnya di sini ) dan saya mencoba memahami bagaimana mereka bekerja dan kapan saya harus menggunakannya.
Sejauh yang saya mengerti, move constructor digunakan untuk mengurangi masalah kinerja yang disebabkan oleh menyalin objek besar. Halaman wikipedia mengatakan: "Masalah kinerja kronis dengan C ++ 03 adalah salinan dalam yang mahal dan tidak perlu yang dapat terjadi secara implisit ketika objek dilewatkan oleh nilai."
Saya biasanya mengatasi situasi seperti itu
- dengan melewati objek dengan referensi, atau
- dengan menggunakan pointer pintar (mis. boost :: shared_ptr) untuk meneruskan objek (pointer pintar disalin bukan objek).
Apa situasi di mana kedua teknik di atas tidak cukup dan menggunakan konstruktor bergerak lebih nyaman?
c++
programming-practices
Giorgio
sumber
sumber
shared_ptr
hanya demi menyalin cepat) dan jika memindahkan semantik dapat mencapai hal yang sama dengan hampir tidak ada coding, semantik, dan kebersihan-penalti.Jawaban:
Pindahkan semantik memperkenalkan seluruh dimensi ke C ++ - ini bukan hanya memungkinkan Anda mengembalikan nilai dengan murah.
Misalnya, tanpa langkah-semantik
std::unique_ptr
tidak berfungsi - lihatstd::auto_ptr
, yang sudah usang dengan pengenalan langkah-semantik dan dihapus di C ++ 17. Memindahkan sumber daya sangat berbeda dengan menyalinnya. Hal ini memungkinkan transfer kepemilikan barang unik.Sebagai contoh, jangan melihat
std::unique_ptr
, karena cukup dibahas. Mari kita lihat, katakanlah, Objek Penyangga Vertex di OpenGL. Buffer vertex mewakili memori pada GPU - itu perlu dialokasikan dan dideallocated menggunakan fungsi khusus, mungkin memiliki kendala ketat pada berapa lama itu bisa hidup. Penting juga bahwa hanya satu pemilik yang menggunakannya.Sekarang, ini bisa dilakukan dengan
std::shared_ptr
- tetapi sumber daya ini tidak untuk dibagikan. Ini membuatnya membingungkan untuk menggunakan pointer bersama. Anda bisa menggunakanstd::unique_ptr
, tetapi itu masih membutuhkan semantik bergerak.Jelas, saya belum menerapkan konstruktor bergerak, tetapi Anda mendapatkan idenya.
Yang relevan di sini adalah bahwa beberapa sumber tidak dapat disalin . Anda dapat membagikan petunjuk alih-alih memindahkan, tetapi kecuali jika Anda menggunakan unique_ptr, ada masalah kepemilikan. Bermanfaat untuk menjadi sejelas mungkin seperti apa maksud dari kode itu, jadi seorang pembangun-konstruktor mungkin adalah pendekatan terbaik.
sumber
Memindahkan semantik tidak selalu merupakan peningkatan besar saat Anda mengembalikan nilai - dan ketika / jika Anda menggunakan
shared_ptr
(atau yang serupa) Anda mungkin pesimisasi secara prematur. Pada kenyataannya, hampir semua kompiler modern melakukan apa yang disebut Return Value Optimization (RVO) dan Named Return Value Optimization (NRVO). Ini berarti bahwa ketika Anda mengembalikan nilai, bukannya benar-benar menyalin nilai sama sekali, mereka hanya melewatkan pointer / referensi tersembunyi ke tempat nilai akan diberikan setelah pengembalian, dan fungsi menggunakannya untuk membuat nilai di mana itu akan berakhir. Standar C ++ menyertakan ketentuan khusus untuk memperbolehkan ini, jadi bahkan jika (misalnya) copy constructor Anda memiliki efek samping yang terlihat, itu tidak diharuskan untuk menggunakan copy constructor untuk mengembalikan nilai. Sebagai contoh:Ide dasar di sini cukup sederhana: buat kelas dengan konten yang cukup, kami lebih suka menghindari menyalinnya, jika mungkin (
std::vector
kami isi dengan 32767 int acak). Kami memiliki ctor salinan eksplisit yang akan menunjukkan kepada kami kapan / jika itu akan disalin. Kami juga memiliki sedikit lebih banyak kode untuk melakukan sesuatu dengan nilai acak dalam objek, sehingga pengoptimal tidak akan (setidaknya dengan mudah) menghilangkan segala sesuatu tentang kelas hanya karena ia tidak melakukan apa-apa.Kami kemudian memiliki beberapa kode untuk mengembalikan salah satu objek ini dari suatu fungsi, dan kemudian menggunakan penjumlahan untuk memastikan objek benar-benar dibuat, tidak hanya diabaikan sepenuhnya. Ketika kami menjalankannya, paling tidak dengan kompiler terbaru / modern, kami menemukan bahwa copy constructor yang kami tulis tidak pernah berjalan sama sekali - dan ya, saya cukup yakin bahwa bahkan salinan cepat dengan yang
shared_ptr
masih lebih lambat daripada tidak menyalin. sama sekali.Bergerak memungkinkan Anda melakukan banyak hal yang tidak dapat Anda lakukan (langsung) secara adil. Pertimbangkan bagian "gabungan" dari semacam gabungan eksternal - Anda memiliki, katakanlah, 8 file yang akan Anda gabungkan bersama. Idealnya Anda ingin menempatkan semua 8 file tersebut ke dalam
vector
- tetapi karenavector
( sejak C ++ 03) harus dapat menyalin elemen, danifstream
tidak dapat disalin, Anda terjebak dengan beberapaunique_ptr
/shared_ptr
, atau sesuatu pada urutan itu untuk dapat menempatkan mereka dalam vektor. Perhatikan bahwa bahkan jika (misalnya) kita memberireserve
ruang dalamvector
jadi kami yakin bahwa kamiifstream
tidak akan pernah benar-benar disalin, kompiler tidak akan tahu itu, jadi kode tidak akan dikompilasi meskipun kita tahu konstruktor salinan tidak akan pernah menjadi tetap digunakan.Meskipun masih tidak dapat disalin, dalam C ++ 11 sebuah
ifstream
dapat dipindahkan. Dalam hal ini, objek mungkin tidak akan pernah dipindahkan, tetapi fakta bahwa mereka bisa jika perlu membuat compiler senang, sehingga kita dapat menempatkanifstream
objek kita secaravector
langsung, tanpa ada peretas pointer pintar.Sebuah vektor yang tidak memperluas adalah contoh yang cukup baik dari waktu bahwa langkah semantik benar-benar dapat menjadi / adalah meskipun berguna. Dalam hal ini, RVO / NRVO tidak akan membantu, karena kami tidak berurusan dengan nilai pengembalian dari suatu fungsi (atau yang serupa). Kami memiliki satu vektor yang menahan beberapa objek, dan kami ingin memindahkan objek-objek itu ke dalam memori baru yang lebih besar.
Di C ++ 03, itu dilakukan dengan membuat salinan objek di memori baru, lalu menghancurkan objek lama di memori lama. Membuat semua salinan itu hanya untuk membuang yang lama, bagaimanapun, cukup membuang waktu. Di C ++ 11, Anda bisa berharap mereka akan dipindahkan. Ini biasanya memungkinkan kita, pada dasarnya, melakukan salinan dangkal alih-alih salinan yang dalam (umumnya jauh lebih lambat). Dengan kata lain, dengan string atau vektor (hanya untuk beberapa contoh) kami hanya menyalin pointer (s) di objek, daripada membuat salinan dari semua data yang merujuk pointer.
sumber
Mempertimbangkan:
Ketika menambahkan string ke v, itu akan berkembang sesuai kebutuhan, dan pada setiap realokasi string harus disalin. Dengan memindahkan konstruktor, ini pada dasarnya bukan masalah.
Tentu saja, Anda juga bisa melakukan sesuatu seperti:
Tapi itu akan bekerja dengan baik hanya karena
std::unique_ptr
mengimplementasikan konstruktor bergerak.Menggunakan
std::shared_ptr
hanya masuk akal dalam situasi (jarang) ketika Anda benar-benar memiliki kepemilikan bersama.sumber
string
kita memiliki contoh diFoo
mana ia memiliki 30 anggota data? Theunique_ptr
Versi tidak akan lebih efisien?Nilai pengembalian adalah tempat paling sering saya ingin melewati nilai alih-alih semacam referensi. Mampu dengan cepat mengembalikan objek 'di stack' tanpa penalti performa yang besar akan menyenangkan. Di sisi lain, itu tidak terlalu sulit untuk mengatasi ini (pointer bersama sangat mudah digunakan ...), jadi saya tidak yakin itu benar-benar layak melakukan pekerjaan ekstra pada objek saya hanya untuk dapat melakukan ini.
sumber