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:
#include <vector>
#include <numeric>
#include <iostream>
#include <stdlib.h>
#include <algorithm>
#include <iterator>
class X {
std::vector<int> a;
public:
X() {
std::generate_n(std::back_inserter(a), 32767, ::rand);
}
X(X const &x) {
a = x.a;
std::cout << "Copy ctor invoked\n";
}
int sum() { return std::accumulate(a.begin(), a.end(), 0); }
};
X func() {
return X();
}
int main() {
X x = func();
std::cout << "sum = " << x.sum();
return 0;
};
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 karena vector
( sejak C ++ 03) harus dapat menyalin elemen, dan ifstream
tidak dapat disalin, Anda terjebak dengan beberapa unique_ptr
/ shared_ptr
, atau sesuatu pada urutan itu untuk dapat menempatkan mereka dalam vektor. Perhatikan bahwa bahkan jika (misalnya) kita memberi reserve
ruang dalam vector
jadi kami yakin bahwa kami ifstream
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 menempatkan ifstream
objek kita secara vector
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.
shared_ptr
hanya demi menyalin cepat) dan jika memindahkan semantik dapat mencapai hal yang sama dengan hampir tidak ada coding, semantik, dan kebersihan-penalti.