Seperti banyak orang hari ini saya telah mencoba berbagai fitur yang dibawa oleh C ++ 11. Salah satu favorit saya adalah "range-based for loop".
Aku mengerti itu:
for(Type& v : a) { ... }
Setara dengan:
for(auto iv = begin(a); iv != end(a); ++iv)
{
Type& v = *iv;
...
}
Dan itu begin()
hanya mengembalikan a.begin()
kontainer standar.
Tetapi bagaimana jika saya ingin membuat tipe kustom saya "berbasis-rentang untuk loop" -adar ?
Haruskah saya hanya mengkhususkan begin()
dan end()
?
Jika tipe khusus saya milik namespace xml
, apakah saya harus mendefinisikan xml::begin()
atau std::begin()
?
Singkatnya, apa pedoman untuk melakukan itu?
c++
for-loop
c++11
customization
sebelum
sumber
sumber
begin/end
atau teman, statis atau gratisbegin/end
. Hanya berhati-hatilah di namespace mana Anda meletakkan fungsi gratis: stackoverflow.com/questions/28242073/…for( auto x : range<float>(0,TWO_PI, 0.1F) ) { ... }
. Saya ingin tahu bagaimana Anda mengatasi kenyataan bahwa `´operator! = ()` `Sulit untuk didefinisikan. Dan bagaimana dengan dereferencing (*__begin
) dalam kasus ini? Saya pikir itu akan menjadi kontribusi yang bagus jika seseorang menunjukkan kepada kita bagaimana hal itu dilakukan!Jawaban:
Standar telah diubah sejak pertanyaan (dan sebagian besar jawaban) diposting dalam resolusi laporan cacat ini .
Cara membuat
for(:)
loop bekerja pada tipe AndaX
sekarang adalah salah satu dari dua cara:Buat anggota
X::begin()
danX::end()
kembalikan sesuatu yang bertindak seperti iteratorBuat fungsi gratis
begin(X&)
danend(X&)
yang mengembalikan sesuatu yang bertindak seperti iterator, di namespace yang sama dengan tipe AndaX
.¹Dan serupa untuk
const
variasi. Ini akan bekerja pada kompiler yang mengimplementasikan perubahan laporan cacat, dan kompiler yang tidak.Objek yang dikembalikan tidak harus benar-benar menjadi iterator. The
for(:)
Loop, tidak seperti sebagian besar C ++ standar, yang ditentukan untuk memperluas untuk sesuatu yang setara dengan :menjadi:
di mana variabel yang dimulai dengan
__
hanya untuk eksposisi, danbegin_expr
danend_expr
adalah sihir yang memanggilbegin
/end
.²Persyaratan pada nilai pengembalian awal / akhir sederhana: Anda harus kelebihan pre-
++
, memastikan ekspresi inisialisasi valid, biner!=
yang dapat digunakan dalam konteks boolean, unary*
yang mengembalikan sesuatu yang dapat Anda tetapkan-inisialisasirange_declaration
dengan, dan mengekspos publik destruktor.Melakukannya dengan cara yang tidak kompatibel dengan iterator mungkin merupakan ide yang buruk, karena iterasi C ++ di masa depan mungkin relatif lebih berani tentang memecahkan kode Anda jika Anda melakukannya.
Sebagai tambahan, ada kemungkinan wajar bahwa revisi standar di masa depan akan memungkinkan
end_expr
untuk mengembalikan jenis yang berbeda daribegin_expr
. Ini berguna karena memungkinkan evaluasi "lazy-end" (seperti mendeteksi null-termination) yang mudah dioptimalkan agar seefisien loop C tulisan tangan, dan keuntungan serupa lainnya.¹ Perhatikan bahwa
for(:)
loop menyimpan sementara apa pun dalamauto&&
variabel, dan memberikannya kepada Anda sebagai nilai. Anda tidak dapat mendeteksi jika Anda mengulanginya sementara (atau nilai lainnya); kelebihan seperti itu tidak akan dipanggil denganfor(:)
loop. Lihat [stmt.ranged] 1.2-1.3 dari n4527.² Baik panggil metode
begin
/end
, atau pencarian fungsi bebas ADL sajabegin
/end
, atau sulap untuk dukungan larik gaya-C. Catatan yangstd::begin
tidak dipanggil kecualirange_expression
mengembalikan objek bertipenamespace std
atau bergantung pada yang sama.Di c ++ 17 rentang-untuk ekspresi telah diperbarui
dengan jenis
__begin
dan__end
telah dipisahkan.Ini memungkinkan iterator akhir untuk tidak menjadi tipe yang sama seperti mulai. Jenis iterator akhir Anda bisa berupa "sentinel" yang hanya mendukung
!=
dengan tipe iterator begin.Contoh praktis mengapa ini berguna adalah bahwa iterator akhir Anda dapat membaca "periksa Anda
char*
untuk melihat apakah itu menunjuk'0'
" ketika==
dengan achar*
. Ini memungkinkan rentang C ++ untuk ekspresi menghasilkan kode yang optimal ketika iterasi lebih darichar*
buffer null-dihentikan .contoh langsung dalam kompiler tanpa dukungan penuh C ++ 17;
for
loop diperluas secara manual.sumber
begin
danend
fungsi yang berbeda dari yang tersedia dalam kode normal. Mungkin mereka kemudian bisa menjadi sangat khusus untuk berperilaku berbeda (yaitu lebih cepat dengan mengabaikan argumen akhir untuk mendapatkan optimalisasi maksimal yang mungkin). Tapi saya tidak cukup baik dengan ruang nama untuk memastikan bagaimana melakukan ini.begin(X&&)
. Sementara ditangguhkan di udara olehauto&&
dalam rentang berbasis, danbegin
selalu disebut dengan nilai (__range
).Saya menulis jawaban saya karena beberapa orang mungkin lebih senang dengan contoh kehidupan nyata yang sederhana tanpa menyertakan STL.
Saya punya implementasi array data hanya polos saya untuk beberapa alasan, dan saya ingin menggunakan rentang berdasarkan untuk loop. Ini solusinya:
Maka contoh penggunaannya:
sumber
const
kualifikasi pengembalianconst DataType& operator*()
, dan membiarkan pengguna memilih untuk menggunakanconst auto&
atauauto&
? Terima kasih, jawaban yang bagus;)Bagian yang relevan dari standar adalah 6.5.4 / 1:
Jadi, Anda dapat melakukan salah satu dari yang berikut:
begin
danend
fungsi anggotabegin
danend
membebaskan fungsi-fungsi yang akan ditemukan oleh ADL (versi yang disederhanakan: letakkan di namespace yang sama dengan kelas)std::begin
danstd::end
std::begin
tetap memanggilbegin()
fungsi anggota, jadi jika Anda hanya menerapkan salah satu di atas, maka hasilnya akan sama tidak peduli yang mana yang Anda pilih. Itu adalah hasil yang sama untuk rentang berbasis untuk loop, dan juga hasil yang sama untuk kode fana belaka yang tidak memiliki aturan resolusi nama magis sendiri sehingga hanyausing std::begin;
diikuti oleh panggilan yang tidak memenuhi syarat untukbegin(a)
.Jika Anda menerapkan fungsi anggota dan fungsi ADL, maka rentang berbasis untuk loop harus memanggil fungsi anggota, sedangkan manusia biasa akan memanggil fungsi ADL. Pastikan mereka melakukan hal yang sama dalam kasus itu!
Jika hal yang Anda tulis mengimplementasikan antarmuka wadah, maka itu akan memiliki
begin()
danend()
fungsi anggota sudah, yang seharusnya sudah cukup. Jika rentang yang bukan wadah (yang akan menjadi ide bagus jika tidak berubah atau jika Anda tidak tahu ukurannya di depan), Anda bebas untuk memilih.Dari opsi yang Anda layangkan, perhatikan bahwa Anda tidak boleh kelebihan beban
std::begin()
. Anda diizinkan untuk mengkhususkan templat standar untuk jenis yang ditentukan pengguna, tetapi selain itu, menambahkan definisi ke namespace std adalah perilaku yang tidak ditentukan. Tapi bagaimanapun, mengkhususkan fungsi standar adalah pilihan yang buruk jika hanya karena kurangnya spesialisasi fungsi parsial berarti Anda hanya dapat melakukannya untuk satu kelas, bukan untuk templat kelas.sumber
!=
, awalan++
dan unary*
. Mungkin tidak bijaksana untuk menerapkanbegin()
danend()
fungsi anggota atau fungsi ADL non-anggota yang mengembalikan apa pun selain iterator, tapi saya pikir itu legal. Spesialisasistd::begin
untuk mengembalikan non-iterator adalah UB, saya pikir.Sejauh yang saya tahu, itu sudah cukup. Anda juga harus memastikan bahwa penunjuk yang bertambah akan didapat dari awal hingga akhir.
Contoh berikutnya (tidak ada versi const awal dan akhir) mengkompilasi dan berfungsi dengan baik.
Berikut adalah contoh lain dengan fungsi begin / end sebagai fungsi. Mereka harus berada di namespace yang sama dengan kelas, karena ADL:
sumber
return v + 10
.&v[10]
referensi lokasi memori hanya melewati array.Jika Anda ingin mendukung iterasi kelas secara langsung dengan anggotanya
std::vector
ataustd::map
anggota, berikut adalah kode untuk itu:sumber
const_iterator
juga dapat diakses dalamauto
(C ++ 11) -yang kompatibel dengan cara melaluicbegin
,cend
, dllDi sini, saya membagikan contoh paling sederhana untuk membuat tipe kustom, yang akan bekerja dengan " range-based for loop ":
Semoga bermanfaat bagi beberapa pengembang pemula seperti saya: p :)
Terima kasih.
sumber
end()
fungsi itu sendiri jelas tidak dereference lokasi memori yang tidak tepat, karena hanya mengambil 'alamat-dari' lokasi memori ini. Menambahkan elemen tambahan berarti Anda akan membutuhkan lebih banyak memori, dan menggunakanyour_iterator::end()
dengan cara apa pun yang akan mengurangi nilai itu tidak akan bekerja dengan iterator lain karena mereka dibangun dengan cara yang sama.return &data[sizeofarray]
IMHO seharusnya mengembalikan data alamat + sizeofarray tapi apa yang saya tahu,data + sizeofarray
akan menjadi cara yang tepat untuk menulis ini.Jawaban Chris Redford juga bekerja untuk kontainer Qt (tentu saja). Berikut ini adalah adaptasi (pemberitahuan saya mengembalikan
constBegin()
, masing-masingconstEnd()
dari metode const_iterator):sumber
Saya ingin menguraikan beberapa bagian dari jawaban @Steve Jessop, yang awalnya saya tidak mengerti. Semoga ini bisa membantu.
https://en.cppreference.com/w/cpp/language/range-for :
Untuk rentang berbasis untuk loop, fungsi anggota dipilih terlebih dahulu.
Tapi untuk
Fungsi ADL dipilih terlebih dahulu.
Contoh:
sumber