Saya percaya Anda memiliki pengamatan yang benar tetapi interpretasi yang salah!
Salinan tidak akan terjadi dengan mengembalikan nilai, karena setiap kompiler pintar yang normal akan menggunakan (N) RVO dalam kasus ini. Dari C ++ 17 ini wajib, jadi Anda tidak dapat melihat salinan apa pun dengan mengembalikan vektor yang dihasilkan lokal dari fungsi.
OK, mari kita bermain sedikit dengan std::vector
dan apa yang akan terjadi selama konstruksi atau dengan mengisinya langkah demi langkah.
Pertama-tama, mari kita buat tipe data yang membuat setiap salinan atau memindahkan terlihat seperti ini:
template <typename DATA >
struct VisibleCopy
{
private:
DATA data;
public:
VisibleCopy( const DATA& data_ ): data{ data_ }
{
std::cout << "Construct " << data << std::endl;
}
VisibleCopy( const VisibleCopy& other ): data{ other.data }
{
std::cout << "Copy " << data << std::endl;
}
VisibleCopy( VisibleCopy&& other ) noexcept : data{ std::move(other.data) }
{
std::cout << "Move " << data << std::endl;
}
VisibleCopy& operator=( const VisibleCopy& other )
{
data = other.data;
std::cout << "copy assign " << data << std::endl;
}
VisibleCopy& operator=( VisibleCopy&& other ) noexcept
{
data = std::move( other.data );
std::cout << "move assign " << data << std::endl;
}
DATA Get() const { return data; }
};
Dan sekarang mari kita mulai beberapa percobaan:
using T = std::vector< VisibleCopy<int> >;
T Get1()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec{ 1,2,3,4 };
std::cout << "End init" << std::endl;
return vec;
}
T Get2()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec(4,0);
std::cout << "End init" << std::endl;
return vec;
}
T Get3()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
T Get4()
{
std::cout << "Start init" << std::endl;
std::vector< VisibleCopy<int> > vec;
vec.reserve(4);
vec.emplace_back(1);
vec.emplace_back(2);
vec.emplace_back(3);
vec.emplace_back(4);
std::cout << "End init" << std::endl;
return vec;
}
int main()
{
auto vec1 = Get1();
auto vec2 = Get2();
auto vec3 = Get3();
auto vec4 = Get4();
// All data as expected? Lets check:
for ( auto& el: vec1 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec2 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec3 ) { std::cout << el.Get() << std::endl; }
for ( auto& el: vec4 ) { std::cout << el.Get() << std::endl; }
}
Apa yang bisa kita amati:
Contoh 1) Kami membuat vektor dari daftar penginisialisasi dan mungkin kami berharap kami akan melihat 4 kali konstruk dan 4 gerakan. Tapi kami mendapat 4 salinan! Kedengarannya agak misterius, tetapi alasannya adalah penerapan daftar penginisialisasi! Sederhananya tidak diperbolehkan untuk pindah dari daftar karena iterator dari daftar adalah const T*
yang membuatnya tidak mungkin untuk memindahkan elemen dari daftar. Jawaban terperinci tentang topik ini dapat ditemukan di sini: initializer_list dan pindahkan semantik
Contoh 2) Dalam hal ini, kami mendapatkan konstruksi awal dan 4 salinan nilainya. Itu tidak ada yang istimewa dan itulah yang bisa kita harapkan.
Contoh 3) Juga di sini, kita membangun dan beberapa bergerak seperti yang diharapkan. Dengan implementasi stl saya, vektor tumbuh dengan faktor 2 setiap kali. Jadi kita melihat konstruk pertama, yang lain dan karena vektor mengubah ukuran dari 1 menjadi 2, kita melihat pergerakan elemen pertama. Saat menambahkan 3 satu, kita melihat ukuran dari 2 ke 4 yang membutuhkan perpindahan dari dua elemen pertama. Semua seperti yang diharapkan!
Contoh 4) Sekarang kami memesan ruang dan mengisi nanti. Sekarang kami tidak memiliki salinan dan tidak bergerak lagi!
Dalam semua kasus, kami tidak melihat gerakan atau salinan dengan mengembalikan vektor ke pemanggil sama sekali! (N) RVO sedang berlangsung dan tidak ada tindakan lebih lanjut diperlukan dalam langkah ini!
Kembali ke pertanyaan Anda:
"Bagaimana menemukan operasi penyalinan palsu C ++"
Seperti yang terlihat di atas, Anda dapat memperkenalkan kelas proxy di antaranya untuk tujuan debugging.
Menjadikan copy-ctor pribadi mungkin tidak berfungsi dalam banyak kasus, karena Anda mungkin memiliki beberapa salinan yang diinginkan dan beberapa yang tersembunyi. Seperti di atas, hanya kode misalnya 4 yang akan berfungsi dengan copy-ctor pribadi! Dan saya tidak bisa menjawab pertanyaan, jika contoh 4 adalah yang tercepat, karena kami mengisi kedamaian dengan kedamaian.
Maaf saya tidak dapat menawarkan solusi umum untuk menemukan salinan "yang tidak diinginkan" di sini. Bahkan jika Anda menggali kode untuk panggilan memcpy
, Anda tidak akan menemukan semuanya juga memcpy
akan dioptimalkan dan Anda melihat langsung beberapa instruksi assembler melakukan pekerjaan tanpa panggilan ke memcpy
fungsi perpustakaan Anda .
Petunjuk saya adalah jangan fokus pada masalah sekecil itu. Jika Anda memiliki masalah kinerja nyata, ambil profiler dan ukur. Ada begitu banyak potensi kinerja pembunuh, sehingga menginvestasikan banyak waktu untuk memcpy
penggunaan palsu tampaknya bukan ide yang berharga.
std::vector
dengan cara apa pun tidak seperti yang seharusnya . Contoh Anda menunjukkan salinan eksplisit, dan itu wajar, dan pendekatan yang benar, (sekali lagi imho) untuk menerapkanstd::move
fungsi seperti yang Anda sarankan sendiri jika salinan bukan yang Anda inginkan. Perhatikan bahwa beberapa kompiler dapat menghilangkan penyalinan jika flag optimisasi dihidupkan, dan vektornya tidak berubah.