for(auto it = m.cbegin(); it != m.cend()/* not hoisted */;/* no increment */){if(must_delete){
m.erase(it++);// or "it = m.erase(it)" since C++11}else{++it;}}
Perhatikan bahwa kami benar-benar menginginkan forloop biasa di sini, karena kami memodifikasi wadah itu sendiri. Lingkaran berbasis rentang harus dicadangkan secara ketat untuk situasi di mana kita hanya peduli pada elemen. Sintaks untuk RBFL membuat ini jelas dengan bahkan tidak mengekspos wadah di dalam tubuh loop.
Edit. Pra-C ++ 11, Anda tidak bisa menghapus const-iterators. Di sana Anda harus mengatakan:
for(std::map<K,V>::iterator it = m.begin(); it != m.end();){/* ... */}
Menghapus elemen dari wadah tidak bertentangan dengan ketegasan elemen. Dengan analogi, itu selalu sangat sah di delete pmana ppointer-ke-konstan. Kekekalan tidak membatasi hidup; nilai const di C ++ masih bisa berhenti ada.
"bahkan tidak mengekspos wadah di dalam tubuh loop" apa maksudmu?
Dani
2
@Ani: Yah, kontras ini dengan konstruksi abad ke-20 for (int i = 0; i < v.size(); i++). Di sini kita harus mengatakan v[i]di dalam loop, yaitu kita harus secara eksplisit menyebutkan wadah. RBFL di sisi lain memperkenalkan variabel loop yang langsung dapat digunakan sebagai nilai, dan jadi tidak ada pengetahuan tentang kontainer yang diperlukan di dalam loop. Ini adalah petunjuk penggunaan RBFL yang dimaksudkan untuk loop yang tidak perlu tahu tentang wadah. Menghapus adalah situasi yang sangat berlawanan, di mana itu semua tentang wadah.
Kerrek SB
3
@skyhisi: Memang. Ini adalah salah satu kegunaan yang sah dari penambahan setelah penambahan: Penambahan pertamait untuk mendapatkan iterator berikutnya yang valid, dan kemudian menghapus yang lama. Itu tidak bekerja sebaliknya!
Kerrek SB
5
Saya membaca di suatu tempat bahwa di C ++ 11, it = v.erase(it);sekarang berfungsi untuk peta juga. Artinya, hapus () pada semua elemen asosiatif sekarang mengembalikan iterator berikutnya. Jadi kludge lama yang membutuhkan kenaikan pasca ++ dalam delete (), tidak diperlukan lagi. Ini (jika benar) adalah Good Thing, karena kludge mengandalkan sihir pasca-kenaikan-timpa-dalam-fungsi-panggilan, "diperbaiki" oleh pengelola pemula untuk mengambil kenaikan dari panggilan fungsi, atau untuk menukarnya ke preincrement "karena itu hanya hal gaya", dll.
Dewi Morgan
3
mengapa Anda akan menelepon it++dalam ifdanelse blok? bukankah itu cukup untuk menyebutnya sekali setelah ini?
nburk
25
Saya pribadi lebih suka pola ini yang sedikit lebih jelas dan sederhana, dengan mengorbankan variabel tambahan:
for(auto it = m.cbegin(), next_it = it; it != m.cend(); it = next_it){++next_it;if(must_delete){
m.erase(it);}}
Keuntungan dari pendekatan ini:
for loop incrementor masuk akal sebagai incrementor;
operasi hapus adalah penghapusan sederhana, daripada dicampur dengan logika kenaikan;
setelah baris pertama dari badan loop, makna itdan next_ittetap tetap di seluruh iterasi, memungkinkan Anda untuk dengan mudah menambahkan pernyataan tambahan merujuk mereka tanpa headscratching apakah mereka akan berfungsi sebagaimana dimaksud (kecuali tentu saja bahwa Anda tidak dapat menggunakan itsetelah menghapusnya) .
Saya bisa memikirkan keuntungan lain sebenarnya, jika loop memanggil ke dalam kode yang menghapus entri yang di-iterasi atau yang sebelumnya (dan loop tidak menyadarinya) itu akan bekerja tanpa ada kerusakan yang dilakukan. Satu-satunya batasan adalah apakah sesuatu menghapus apa yang ditunjukkan oleh next_it atau penerusnya. Daftar / peta yang benar-benar bersih dapat diuji terhadapnya juga.
Larswad
Jawaban ini sederhana dan jelas, bahkan jika loop lebih kompleks dan memiliki beberapa level logika untuk memutuskan apakah akan dihapus atau tidak, atau melakukan berbagai tugas lainnya. Saya sudah mengusulkan edit, untuk membuatnya sedikit lebih sederhana. "next_it" dapat diatur ke "it" di in's forit untuk menghindari kesalahan ketik, dan karena pernyataan init dan iterasi keduanya mengatur dan next_it ke nilai yang sama, Anda tidak perlu mengatakan "next_it = it;" di awal loop.
cdgraham
1
Ingatlah siapa pun yang menggunakan jawaban ini: Anda harus memiliki "++ next_it" di dalam for loop, dan tidak dalam ekspresi iterasi. Jika Anda mencoba memindahkannya ke ekspresi iterasi sebagai "it = next_it ++", maka pada iterasi terakhir, ketika "it" akan disetel sama dengan "m.cend ()", Anda akan mencoba untuk mengulang "next_it" melewati "m.cend ()", yang salah.
cdgraham
6
Singkatnya, "Bagaimana saya menghapus dari peta saat iterasi?"
Dengan impl peta lama: Anda tidak bisa
Dengan impl peta baru: hampir seperti yang disarankan @KerrekSB. Tetapi ada beberapa masalah sintaksis dalam apa yang dia posting.
Dari imp peta GCC (perhatikan GXX_EXPERIMENTAL_CXX0X ):
#ifdef __GXX_EXPERIMENTAL_CXX0X__
// _GLIBCXX_RESOLVE_LIB_DEFECTS// DR 130. Associative erase should return an iterator./**
* @brief Erases an element from a %map.
* @param position An iterator pointing to the element to be erased.
* @return An iterator pointing to the element immediately following
* @a position prior to the element being erased. If no such
* element exists, end() is returned.
*
* This function erases an element, pointed to by the given
* iterator, from a %map. Note that this function only erases
* the element, and that if the element is itself a pointer,
* the pointed-to memory is not touched in any way. Managing
* the pointer is the user's responsibility.
*/iterator
erase(iterator __position){return_M_t.erase(__position);}#else/**
* @brief Erases an element from a %map.
* @param position An iterator pointing to the element to be erased.
*
* This function erases an element, pointed to by the given
* iterator, from a %map. Note that this function only erases
* the element, and that if the element is itself a pointer,
* the pointed-to memory is not touched in any way. Managing
* the pointer is the user's responsibility.
*/void
erase(iterator __position){_M_t.erase(__position);}#endif
Contoh dengan gaya lama dan baru:
#include<iostream>#include<map>#include<vector>#include<algorithm>usingnamespace std;typedefmap<int,int> t_myMap;typedefvector<t_myMap::key_type> t_myVec;int main(){
cout <<"main() ENTRY"<< endl;
t_myMap mi;
mi.insert(t_myMap::value_type(1,1));
mi.insert(t_myMap::value_type(2,1));
mi.insert(t_myMap::value_type(3,1));
mi.insert(t_myMap::value_type(4,1));
mi.insert(t_myMap::value_type(5,1));
mi.insert(t_myMap::value_type(6,1));
cout <<"Init"<< endl;for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
cout <<'\t'<< i->first <<'-'<< i->second << endl;
t_myVec markedForDeath;for(t_myMap::const_iterator it = mi.begin(); it != mi.end(); it++)if(it->first >2&& it->first <5)
markedForDeath.push_back(it->first);for(size_t i =0; i < markedForDeath.size(); i++)// old erase, returns void...
mi.erase(markedForDeath[i]);
cout <<"after old style erase of 3 & 4.."<< endl;for(t_myMap::const_iterator i = mi.begin(); i != mi.end(); i++)
cout <<'\t'<< i->first <<'-'<< i->second << endl;for(auto it = mi.begin(); it != mi.end();){if(it->first ==5)// new erase() that returns iter..
it = mi.erase(it);else++it;}
cout <<"after new style erase of 5"<< endl;// new cend/cbegin and lambda..
for_each(mi.cbegin(), mi.cend(),[](t_myMap::const_reference it){cout <<'\t'<< it.first <<'-'<< it.second << endl;});return0;}
cetakan:
main() ENTRY
Init1-12-13-14-15-16-1
after old style erase of 3&4..1-12-15-16-1
after new style erase of 51-12-16-1Process returned 0(0x0) execution time :0.021 s
Press any key to continue.
Saya tidak mengerti. Ada apa dengan ini mi.erase(it++);?
lvella
1
@lla melihat op. "Jika saya menggunakan map.erase itu akan membatalkan iterators".
Kashyap
Metode baru Anda tidak akan berfungsi jika setelah dihapus, peta menjadi kosong. Dalam hal ini, iterator akan dibatalkan. Jadi, segera setelah dihapus, lebih baik memasukkan if(mi.empty()) break;.
Rahat Zaman
4
Draf C ++ 20 berisi fungsi kenyamanan std::erase_if.
Jadi Anda bisa menggunakan fungsi itu untuk melakukannya sebagai satu-liner.
std::map<K, V> map_obj;//calls needs_removing for each element and erases it, if true was reuturned
std::erase_if(map_obj,needs_removing);//if you need to pass only part of the key/value pair
std::erase_if(map_obj,[](auto& kv){return needs_removing(kv.first);});
Cukup sedih, kan? Cara saya biasanya melakukannya adalah membangun wadah iterator bukannya menghapus selama traversal. Kemudian putar melalui wadah dan gunakan map.erase ()
std::map<K,V>map;
std::list< std::map<K,V>::iterator> iteratorList;for(auto i :map){if( needs_removing(i)){
iteratorList.push_back(i);}}for(auto i : iteratorList){map.erase(*i)}
Jawaban:
Ungkapan asosiasi asosiatif-wadah standar:
Perhatikan bahwa kami benar-benar menginginkan
for
loop biasa di sini, karena kami memodifikasi wadah itu sendiri. Lingkaran berbasis rentang harus dicadangkan secara ketat untuk situasi di mana kita hanya peduli pada elemen. Sintaks untuk RBFL membuat ini jelas dengan bahkan tidak mengekspos wadah di dalam tubuh loop.Edit. Pra-C ++ 11, Anda tidak bisa menghapus const-iterators. Di sana Anda harus mengatakan:
Menghapus elemen dari wadah tidak bertentangan dengan ketegasan elemen. Dengan analogi, itu selalu sangat sah di
delete p
manap
pointer-ke-konstan. Kekekalan tidak membatasi hidup; nilai const di C ++ masih bisa berhenti ada.sumber
for (int i = 0; i < v.size(); i++)
. Di sini kita harus mengatakanv[i]
di dalam loop, yaitu kita harus secara eksplisit menyebutkan wadah. RBFL di sisi lain memperkenalkan variabel loop yang langsung dapat digunakan sebagai nilai, dan jadi tidak ada pengetahuan tentang kontainer yang diperlukan di dalam loop. Ini adalah petunjuk penggunaan RBFL yang dimaksudkan untuk loop yang tidak perlu tahu tentang wadah. Menghapus adalah situasi yang sangat berlawanan, di mana itu semua tentang wadah.it
untuk mendapatkan iterator berikutnya yang valid, dan kemudian menghapus yang lama. Itu tidak bekerja sebaliknya!it = v.erase(it);
sekarang berfungsi untuk peta juga. Artinya, hapus () pada semua elemen asosiatif sekarang mengembalikan iterator berikutnya. Jadi kludge lama yang membutuhkan kenaikan pasca ++ dalam delete (), tidak diperlukan lagi. Ini (jika benar) adalah Good Thing, karena kludge mengandalkan sihir pasca-kenaikan-timpa-dalam-fungsi-panggilan, "diperbaiki" oleh pengelola pemula untuk mengambil kenaikan dari panggilan fungsi, atau untuk menukarnya ke preincrement "karena itu hanya hal gaya", dll.it++
dalamif
danelse
blok? bukankah itu cukup untuk menyebutnya sekali setelah ini?Saya pribadi lebih suka pola ini yang sedikit lebih jelas dan sederhana, dengan mengorbankan variabel tambahan:
Keuntungan dari pendekatan ini:
it
dannext_it
tetap tetap di seluruh iterasi, memungkinkan Anda untuk dengan mudah menambahkan pernyataan tambahan merujuk mereka tanpa headscratching apakah mereka akan berfungsi sebagaimana dimaksud (kecuali tentu saja bahwa Anda tidak dapat menggunakanit
setelah menghapusnya) .sumber
Singkatnya, "Bagaimana saya menghapus dari peta saat iterasi?"
Dari imp peta GCC (perhatikan GXX_EXPERIMENTAL_CXX0X ):
Contoh dengan gaya lama dan baru:
cetakan:
sumber
mi.erase(it++);
?if(mi.empty()) break;
.Draf C ++ 20 berisi fungsi kenyamanan
std::erase_if
.Jadi Anda bisa menggunakan fungsi itu untuk melakukannya sebagai satu-liner.
sumber
Cukup sedih, kan? Cara saya biasanya melakukannya adalah membangun wadah iterator bukannya menghapus selama traversal. Kemudian putar melalui wadah dan gunakan map.erase ()
sumber
Dengan asumsi C ++ 11, di sini adalah badan loop satu-liner, jika ini konsisten dengan gaya pemrograman Anda:
Beberapa perubahan gaya minor lainnya:
Map::const_iterator
) bila memungkinkan / nyaman, selama digunakanauto
.using
untuk tipe template, untuk membuat tipe tambahan (Map::const_iterator
) lebih mudah dibaca / dipelihara.sumber