Baru-baru ini, saya memiliki yang berikut ini
struct data {
std::vector<int> V;
};
data get_vector(int n)
{
std::vector<int> V(n,0);
return {V};
}
Masalah dengan kode ini adalah ketika struct dibuat salinan terjadi dan solusinya adalah menulis kembali {std :: move (V)}
Apakah ada linter atau penganalisa kode yang akan mendeteksi operasi penyalinan palsu seperti itu? Baik cppcheck, cpplint, atau clang-rapi tidak bisa melakukannya.
EDIT: Beberapa poin untuk memperjelas pertanyaan saya:
- Saya tahu bahwa operasi penyalinan terjadi karena saya menggunakan compiler explorer dan ini menunjukkan panggilan ke memcpy .
- Saya dapat mengidentifikasi bahwa operasi penyalinan terjadi dengan melihat standar ya. Tapi ide awal saya yang salah adalah bahwa kompiler akan mengoptimalkan salinan ini. Saya salah.
- Ini (kemungkinan) bukan masalah kompiler karena dentang dan gcc menghasilkan kode yang menghasilkan memcpy .
- Memcpy mungkin murah, tapi saya tidak bisa membayangkan keadaan di mana menyalin memori dan menghapus yang asli lebih murah daripada melewati pointer dengan std :: move .
- Menambahkan std :: move adalah operasi dasar. Saya akan membayangkan bahwa penganalisa kode akan dapat menyarankan koreksi ini.
c++
code-analysis
static-code-analysis
cppcheck
Mathieu Dutour Sikiric
sumber
sumber
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.Jawaban:
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:
Dan sekarang mari kita mulai beberapa percobaan:
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 semantikContoh 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:
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 jugamemcpy
akan dioptimalkan dan Anda melihat langsung beberapa instruksi assembler melakukan pekerjaan tanpa panggilan kememcpy
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.sumber
Apakah Anda memasukkan aplikasi lengkap Anda ke dalam compiler explorer, dan apakah Anda mengaktifkan optimasi? Jika tidak, maka apa yang Anda lihat di kompiler explorer mungkin atau mungkin bukan apa yang terjadi dengan aplikasi Anda.
Salah satu masalah dengan kode yang Anda poskan adalah bahwa Anda pertama kali membuat
std::vector
, dan kemudian menyalinnya ke instancedata
. Akan lebih baik menginisialisasidata
dengan vektor:Juga, jika Anda hanya memberikan definisi kompiler explorer
data
danget_vector()
, dan tidak ada yang lain, itu harus mengharapkan yang lebih buruk. Jika Anda benar-benar memberikannya beberapa kode sumber yang digunakanget_vector()
, maka lihat perakitan apa yang dihasilkan untuk kode sumber itu. Lihat contoh ini untuk apa modifikasi di atas ditambah penggunaan aktual ditambah optimisasi kompiler dapat menyebabkan kompiler menghasilkan.sumber