Saya perlu melalui satu set dan menghapus elemen yang memenuhi kriteria yang telah ditentukan.
Ini adalah kode tes yang saya tulis:
#include <set>
#include <algorithm>
void printElement(int value) {
std::cout << value << " ";
}
int main() {
int initNum[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
std::set<int> numbers(initNum, initNum + 10);
// print '0 1 2 3 4 5 6 7 8 9'
std::for_each(numbers.begin(), numbers.end(), printElement);
std::set<int>::iterator it = numbers.begin();
// iterate through the set and erase all even numbers
for (; it != numbers.end(); ++it) {
int n = *it;
if (n % 2 == 0) {
// wouldn't invalidate the iterator?
numbers.erase(it);
}
}
// print '1 3 5 7 9'
std::for_each(numbers.begin(), numbers.end(), printElement);
return 0;
}
Pada awalnya, saya berpikir bahwa menghapus elemen dari set sementara iterasi melalui itu akan membatalkan iterator, dan kenaikan pada for loop akan memiliki perilaku yang tidak terdefinisi. Meskipun demikian, saya menjalankan kode tes ini dan semuanya berjalan dengan baik, dan saya tidak bisa menjelaskan mengapa.
Pertanyaan saya: Apakah ini perilaku yang ditentukan untuk set std atau apakah implementasi ini spesifik? Saya menggunakan gcc 4.3.3 di ubuntu 10.04 (versi 32-bit), omong-omong.
Terima kasih!
Solusi yang diusulkan:
Apakah ini cara yang benar untuk mengulang dan menghapus elemen dari set?
while(it != numbers.end()) {
int n = *it;
if (n % 2 == 0) {
// post-increment operator returns a copy, then increment
numbers.erase(it++);
} else {
// pre-increment operator increments, then return
++it;
}
}
Sunting: SOLUSI YANG DITENTUKAN
Saya menemukan solusi yang tampaknya lebih elegan bagi saya, meskipun tidak persis sama.
while(it != numbers.end()) {
// copy the current iterator then increment it
std::set<int>::iterator current = it++;
int n = *current;
if (n % 2 == 0) {
// don't invalidate iterator it, because it is already
// pointing to the next element
numbers.erase(current);
}
}
Jika ada beberapa kondisi pengujian di dalam sementara, masing-masing harus meningkatkan iterator. Saya lebih suka kode ini karena iterator hanya bertambah di satu tempat , membuat kode lebih sedikit rawan kesalahan dan lebih mudah dibaca.
++it
seharusnya lebih efisien daripadait++
karena tidak memerlukan penggunaan salinan sementara iterator yang tidak terlihat. Versi Kornel sementara lebih lama memastikan bahwa elemen-elemen yang tidak disaring diulang dengan lebih efisien.Jawaban:
Ini tergantung pada implementasi:
Standar 23.1.2.8:
Mungkin Anda bisa mencoba ini - ini sesuai standar:
Perhatikan bahwa ini ++ adalah postfix, karena itu ia melewatkan posisi lama untuk dihapus, tetapi pertama-tama melompat ke posisi yang lebih baru karena operator.
Pembaruan 2015.10.27: C ++ 11 telah menyelesaikan cacat.
iterator erase (const_iterator position);
kembalikan iterator ke elemen yang mengikuti elemen terakhir dihapus (atauset::end
, jika elemen terakhir dihapus). Jadi gaya C ++ 11 adalah:sumber
deque
pada MSVC2013. Entah implementasi mereka buggy atau ada persyaratan lain yang mencegah hal ini bekerjadeque
. Spesifikasi STL berbelit-belit sehingga Anda tidak bisa mengharapkan semua implementasi untuk mengikutinya, apalagi programmer biasa Anda menghafalnya. STL adalah monster yang melebihi penjinakan, dan karena tidak ada implementasi yang unik (dan test suites, jika ada, tampaknya tidak mencakup kasus-kasus yang jelas seperti menghapus elemen dalam satu lingkaran), yang menjadikan STL mainan rapuh mengkilap yang dapat naik dengan bang ketika Anda melihatnya menyamping.Jika Anda menjalankan program Anda melalui valgrind, Anda akan melihat banyak kesalahan baca. Dengan kata lain, ya, iterator sedang tidak valid, tetapi Anda beruntung dalam contoh Anda (atau benar-benar sial, karena Anda tidak melihat efek negatif dari perilaku yang tidak terdefinisi). Salah satu solusi untuk ini adalah membuat iterator sementara, menambah temp, menghapus iterator target, lalu mengatur target ke temp. Misalnya, tulis ulang loop Anda sebagai berikut:
sumber
while
loop. yaitufor ( ; it != numbers.end(); )
lebih baik terlihat denganwhile (it != numbers.end())
Anda salah mengerti apa arti "perilaku tidak terdefinisi". Perilaku yang tidak terdefinisi tidak berarti "jika Anda melakukan ini, program Anda akan macet atau menghasilkan hasil yang tidak terduga." Ini berarti "jika Anda melakukan ini, program Anda bisa crash atau menghasilkan hasil yang tidak terduga", atau melakukan hal lain, tergantung pada kompiler Anda, sistem operasi Anda, fase bulan, dll.
Jika sesuatu dijalankan tanpa menabrak dan berperilaku seperti yang Anda harapkan, itu bukan bukti bahwa itu bukan perilaku yang tidak terdefinisi. Semua itu membuktikan bahwa perilakunya kebetulan diamati selama menjalankan tertentu setelah dikompilasi dengan kompiler tertentu pada sistem operasi tertentu.
Menghapus elemen dari set akan membatalkan iterator ke elemen yang terhapus. Menggunakan iterator yang tidak valid adalah perilaku yang tidak terdefinisi. Kebetulan bahwa perilaku yang diamati adalah apa yang Anda maksudkan dalam contoh khusus ini; itu tidak berarti bahwa kodenya benar.
sumber
if (n > 2 && n < 7 )
maka saya mendapatkan 0 1 2 4 7 8 9. - Hasil khusus di sini mungkin lebih tergantung pada detail implementasi metode hapus dan mengatur iterator, daripada pada fase bulan (bukan yang satu harus bergantung pada detail implementasi). ;)std::set::erase
untuk mengembalikan iterator, sehingga kode MSVC Anda akan naik dengan bang ketika dikompilasi oleh gcc", atau "Microsoft melakukan pemeriksaan terikatstd::bitset::operator[]
sehingga algoritme bitet yang dioptimalkan dengan cermat Anda akan melambat ke merangkak saat dikompilasi dengan MSVC ". STL tidak memiliki implementasi yang unik dan speknya adalah kekacauan yang membesar secara eksponensial, jadi tidak heran menghapus elemen dari dalam loop memerlukan keahlian programmer senior ...Hanya untuk memperingatkan, bahwa dalam kasus wadah deque, semua solusi yang memeriksa kesetaraan deer iterator untuk angka.end () kemungkinan akan gagal pada gcc 4.8.4. Yaitu, menghapus elemen dari deque umumnya membatalkan pointer ke numbers.end ():
Keluaran:
Perhatikan bahwa sementara transformasi deque benar dalam kasus khusus ini, penunjuk akhir telah dibatalkan di sepanjang jalan. Dengan deque dengan ukuran berbeda kesalahannya lebih jelas:
Keluaran:
Berikut adalah salah satu cara untuk memperbaikinya:
sumber
do not trust an old remembered dq.end() value, always compare to a new call to dq.end()
.C ++ 20 akan memiliki "penghapusan kontainer seragam", dan Anda akan dapat menulis:
Dan yang akan bekerja untuk
vector
,set
,deque
, dll Lihat cppReference untuk info lebih lanjut.sumber
Perilaku ini spesifik untuk implementasi. Untuk menjamin kebenaran iterator Anda harus menggunakan "it = numbers.erase (it);" pernyataan jika Anda perlu menghapus elemen dan hanya menambahkan iterator dalam kasus lain.
sumber
Set<T>::erase
versi tidak mengembalikan iterator.gcc 4.8
denganc++1y
memiliki bug dalam penghapusan.it = collection.erase(it);
seharusnya berfungsi, tetapi mungkin lebih aman untuk digunakancollection.erase(it++);
Saya pikir menggunakan metode STL '
remove_if
' dari dapat membantu untuk mencegah beberapa masalah aneh ketika mencoba untuk mencoba menghapus objek yang dibungkus oleh iterator.Solusi ini mungkin kurang efisien.
Katakanlah kita memiliki beberapa jenis wadah, seperti vektor atau daftar yang disebut m_bullets:
'
it
' adalah iterator yang 'remove_if
' kembali, argumen ketiga adalah fungsi lambda yang dieksekusi pada setiap elemen wadah. Karena wadah berisiBullet::Ptr
, fungsi lambda perlu mendapatkan tipe itu (atau referensi ke tipe itu) diteruskan sebagai argumen.'
remove_if
' menghapus wadah tempat fungsi lambda mengembalikan true dan menggeser konten itu ke awal wadah. The 'it
' menunjuk ke objek yang tidak didefinisikan yang dapat dianggap sampah. Objek dari 'it' ke m_bullets.end () dapat dihapus, karena mereka menempati memori, tetapi mengandung sampah, sehingga metode 'erase' dipanggil pada rentang itu.sumber
Saya menemukan masalah lama yang sama dan menemukan kode di bawah ini lebih dimengerti yang dengan cara per solusi di atas.
sumber