Jika Anda membaca kode suka
auto&& var = foo();
di mana foo
setiap fungsi kembali berdasarkan nilai tipe T
. Kemudian var
adalah nilai referensi tipe nilai T
. Tapi apa maksudnya ini var
? Apakah ini berarti, kita diizinkan mencuri sumber daya var
? Apakah ada situasi yang masuk akal ketika Anda harus auto&&
memberi tahu pembaca tentang kode Anda seperti yang Anda lakukan ketika Anda mengembalikannya unique_ptr<>
untuk memberi tahu bahwa Anda memiliki kepemilikan eksklusif? Dan bagaimana dengan misalnya T&&
kapan T
tipe kelas?
Saya hanya ingin mengerti, jika ada kasus penggunaan lain auto&&
selain yang ada di pemrograman template; seperti yang dibahas dalam contoh di artikel ini Referensi Universal oleh Scott Meyers.
auto&&
? Saya telah berpikir untuk melihat mengapa sebuah range-based untuk loop diperluas untuk digunakanauto&&
sebagai contoh, tetapi belum berhasil. Mungkin siapa pun yang menjawab dapat menjelaskannya.foo
kembali, menyimpan referensi nilai sepertinya kedengarannya seperti UB untuk ne.foo
kekuatan misalnya terlihat seperti:int foo(){return 1;}
.Jawaban:
Dengan menggunakan
auto&& var = <initializer>
Anda mengatakan: Saya akan menerima inisialisasi apa pun terlepas dari apakah itu adalah ekspresi nilai rendah atau nilai dan saya akan menjaga keteguhannya . Ini biasanya digunakan untuk penerusan (biasanya denganT&&
). Alasan ini berhasil adalah karena "referensi universal",auto&&
atauT&&
, akan mengikat apa pun .Anda mungkin berkata, mengapa tidak hanya menggunakan
const auto&
karena itu juga akan mengikat apa pun? Masalah dengan menggunakanconst
referensi adalah ituconst
! Anda tidak akan dapat kemudian mengikatnya ke referensi non-const atau memohon fungsi anggota yang tidak ditandaiconst
.Sebagai contoh, bayangkan Anda ingin mendapatkan
std::vector
, ambil iterator ke elemen pertama dan modifikasi nilai yang ditunjukkan oleh iterator dengan cara:Kode ini akan dikompilasi dengan baik terlepas dari ekspresi initializer. Alternatif untuk
auto&&
gagal dengan cara berikut:Jadi untuk ini,
auto&&
bekerja dengan sempurna! Contoh menggunakanauto&&
seperti ini adalah dalamfor
lingkaran berbasis rentang . Lihat pertanyaan saya yang lain untuk lebih jelasnya.Jika kemudian Anda gunakan
std::forward
padaauto&&
referensi Anda untuk mempertahankan fakta bahwa itu awalnya baik nilai atau nilai, kode Anda mengatakan: Sekarang saya sudah mendapatkan objek Anda dari ekspresi nilai atau nilai, saya ingin mempertahankan mana pun yang menilai itu awalnya sudah jadi saya bisa menggunakannya paling efisien - ini mungkin membatalkannya. Seperti dalam:Hal ini memungkinkan
use_it_elsewhere
untuk menghilangkan nyali untuk kinerja (menghindari salinan) ketika penginisialisasi asli adalah nilai yang dapat dimodifikasi.Apa artinya ini apakah kita bisa atau kapan kita bisa mencuri sumber daya
var
? Yah karenaauto&&
kehendak mengikat apa pun, kita tidak mungkin mencoba untuk merobekvar
nyali diri kita sendiri - itu mungkin nilai yang lebih baik atau bahkan const. Namun kita bisastd::forward
ke fungsi lain yang benar-benar dapat merusak bagian dalamnya. Segera setelah kami melakukan ini, kami harus mempertimbangkanvar
dalam keadaan tidak valid.Sekarang mari kita terapkan ini pada kasus
auto&& var = foo();
, seperti yang diberikan dalam pertanyaan Anda, di mana foo mengembalikanT
nilai berdasarkan. Dalam hal ini kita tahu pasti bahwa jenisvar
akan disimpulkan sebagaiT&&
. Karena kita tahu pasti bahwa itu adalah nilai, kita tidak perlustd::forward
izin untuk mencuri sumber dayanya. Dalam kasus khusus ini, mengetahui bahwafoo
kembali dengan nilai , pembaca harus membacanya sebagai: Saya mengambil referensi nilai untuk sementara kembali darifoo
, jadi saya bisa dengan senang hati pindah dari itu.Sebagai tambahan, saya pikir ada baiknya menyebutkan kapan ekspresi seperti
some_expression_that_may_be_rvalue_or_lvalue
mungkin muncul, selain situasi "baik kode Anda mungkin berubah". Jadi, inilah contoh yang dibuat:Di sini,
get_vector<T>()
adalah ekspresi indah yang bisa berupa nilai atau nilai tergantung pada jenis generikT
. Kami pada dasarnya mengubah jenis pengembalianget_vector
melalui parameter templatfoo
.Ketika kami menelepon
foo<std::vector<int>>
,get_vector
akan kembaliglobal_vec
dengan nilai, yang memberikan ekspresi nilai. Atau, saat kita meneleponfoo<std::vector<int>&>
,get_vector
akan kembaliglobal_vec
dengan referensi, menghasilkan ekspresi nilai yang lebih tinggi.Jika kita melakukannya:
Kami mendapatkan output berikut, seperti yang diharapkan:
Jika Anda adalah untuk mengubah
auto&&
dalam kode untuk setiapauto
,auto&
,const auto&
, atauconst auto&&
maka kita tidak akan mendapatkan hasil yang kita inginkan.Cara alternatif untuk mengubah logika program berdasarkan pada apakah
auto&&
referensi Anda diinisialisasi dengan ekspresi lvalue atau rvalue adalah dengan menggunakan ciri-ciri tipe:sumber
T vec = get_vector<T>();
fungsi dalam foo? Atau apakah saya menyederhanakannya ke tingkat yang tidak masuk akal :)Pertama, saya sarankan membaca jawaban saya ini sebagai bacaan sampingan untuk penjelasan langkah demi langkah tentang bagaimana deduksi argumen templat untuk referensi universal bekerja.
Belum tentu. Bagaimana jika
foo()
tiba-tiba kembali referensi, atau Anda mengubah panggilan tetapi lupa memperbarui penggunaanvar
? Atau jika Anda menggunakan kode generik dan tipe pengembalianfoo()
mungkin berubah tergantung pada parameter Anda?Pikirkan
auto&&
untuk menjadi persis sama denganT&&
ditemplate<class T> void f(T&& v);
, karena (hampir † ) persis seperti itu. Apa yang Anda lakukan dengan referensi universal dalam fungsi, ketika Anda harus meneruskannya atau menggunakannya dengan cara apa pun? Anda menggunakanstd::forward<T>(v)
untuk mendapatkan kembali kategori nilai asli. Jika itu adalah nilai sebelum diteruskan ke fungsi Anda, itu tetap nilai setelah melewatistd::forward
. Jika itu adalah nilai, itu akan menjadi nilai lagi (ingat, referensi nilai yang disebut adalah nilai).Jadi, bagaimana Anda menggunakannya
var
dengan benar dalam mode generik? Gunakanstd::forward<decltype(var)>(var)
. Ini akan bekerja persis sama denganstd::forward<T>(v)
di templat fungsi di atas. Jikavar
aT&&
, Anda akan mendapatkan nilai kembali, dan jika yaT&
, Anda akan mendapatkan nilai kembali.Jadi, kembali ke topik: Apa yang dikatakan oleh
auto&& v = f();
danstd::forward<decltype(v)>(v)
dalam basis kode? Mereka memberi tahu kami bahwav
akan diperoleh dan diteruskan dengan cara yang paling efisien. Ingat, meskipun, setelah meneruskan variabel seperti itu, ada kemungkinan bahwa variabel tersebut dipindahkan-dari, jadi akan lebih salah menggunakannya tanpa menyetel ulang.Secara pribadi, saya menggunakan
auto&&
kode generik ketika saya membutuhkan variabel yang dapat dimodifikasi . Penerusan sempurna nilai ulang dimodifikasi, karena operasi pemindahan berpotensi mencuri nyali. Jika saya hanya ingin menjadi malas (yaitu, tidak mengeja nama jenis bahkan jika saya mengetahuinya) dan tidak perlu memodifikasi (misalnya, ketika hanya mencetak elemen rentang), saya akan tetap menggunakannyaauto const&
.†
auto
adalah sejauh berbeda yangauto v = {1,2,3};
akan membuatv
sebuahstd::initializer_list
, sementaraf({1,2,3})
akan gagal deduksi.sumber
foo()
mengembalikan tipe-nilaiT
, makavar
(ungkapan ini) akan menjadi nilai dan jenisnya (dari ungkapan ini) akan menjadi referensi nilai untukT
(yaituT&&
).Pertimbangkan beberapa tipe
T
yang memiliki konstruktor bergerak, dan asumsikanmenggunakan konstruktor bergerak itu.
Sekarang, mari gunakan referensi perantara untuk mengambil kembali dari
foo
:ini mengesampingkan penggunaan konstruktor bergerak, sehingga nilai kembali harus disalin alih-alih dipindahkan (bahkan jika kita gunakan di
std::move
sini, kita tidak dapat benar-benar bergerak melalui const ref)Namun, jika kita gunakan
konstruktor bergerak masih tersedia.
Dan untuk menjawab pertanyaan Anda yang lain:
Hal pertama, seperti kata Xeo, pada dasarnya saya melewati X seefisien mungkin , apa pun tipe X. Jadi, melihat kode yang menggunakan
auto&&
internal harus berkomunikasi bahwa itu akan menggunakan semantik bergerak secara internal yang sesuai.Ketika templat fungsi mengambil argumen tipe
T&&
, itu mengatakan itu dapat memindahkan objek yang Anda masukkan. Mengembalikanunique_ptr
secara eksplisit memberikan kepemilikan kepada pemanggil; menerimaT&&
dapat menghapus kepemilikan dari penelepon (jika ada agen yang bergerak, dll.).sumber
ref
danrvref
keduanya adalah nilai. Jika Anda ingin memindahkan konstruktor, maka Anda harus menulisT t(std::move(rvref))
.auto const &
?auto&&
dan apa yang Anda katakan kepada pembaca tentang kode Anda dengan menggunakanauto&&
?The
auto &&
sintaks menggunakan dua fitur baru dari C ++ 11:Bagian ini
auto
memungkinkan kompilator menyimpulkan tipe berdasarkan konteks (nilai balik dalam kasus ini). Ini tanpa kualifikasi referensi apa pun (memungkinkan Anda menentukan apakah Anda mauT
,T &
atauT &&
untuk jenis yang disimpulkanT
).Ini
&&
adalah semantik langkah baru. Tipe semantik yang mendukung gerakan mengimplementasikan konstruktorT(T && other)
yang secara optimal memindahkan konten dalam tipe baru. Ini memungkinkan suatu objek untuk menukar representasi internal daripada melakukan salinan yang dalam.Ini memungkinkan Anda untuk memiliki sesuatu seperti:
Begitu:
akan melakukan salinan vektor yang dikembalikan (mahal), tetapi:
akan menukar representasi internal vektor (vektor dari
foo
dan vektor kosong darivar
), sehingga akan lebih cepat.Ini digunakan dalam sintaks for-loop baru:
Di mana for-loop memegang
auto &&
nilai pengembalian darifoo
danitem
merupakan referensi untuk setiap nilai dalamfoo
.sumber
auto&&
tidak akan memindahkan apapun, itu hanya akan membuat referensi. Apakah itu referensi nilai atau nilai tergantung pada ekspresi yang digunakan untuk menginisialisasi itu.std::vector
danstd::string
dapat bergerak. Ini tidak ada hubungannya dengan tipevar
.