Cara memanggil hapus dengan iterator terbalik


181

Saya mencoba melakukan sesuatu seperti ini:

for ( std::list< Cursor::Enum >::reverse_iterator i = m_CursorStack.rbegin(); i != m_CursorStack.rend(); ++i )
{
    if ( *i == pCursor )
    {
        m_CursorStack.erase( i );
        break;
    }
}

Namun menghapus membutuhkan iterator dan bukan iterator terbalik. apakah ada cara untuk mengubah iterator terbalik menjadi iterator biasa atau cara lain untuk menghapus elemen ini dari daftar?


17
Sebagai tambahan, ketika menulis loop seperti ini, jangan berulang kali menghitung iterator akhir seperti yang Anda lakukan di sini i != m_CursorStack.rend(). Sebaliknya, tulislah i = m_CursorStack.rbegin(), end = m_CursorStack.rend(); i != end;. Yaitu, inisialisasi iterator yang dapat Anda pertahankan untuk perbandingan berulang - dengan asumsi bahwa posisi akhir tidak akan berubah sebagai efek samping dari tubuh loop Anda.
seh

Tampak bagi saya bahwa pertanyaan yang jelas di sini adalah mengapa Anda melakukan ini sama sekali. Apa yang Anda peroleh dari melintasi daftar secara terbalik? Apa yang Anda dapatkan dengan menulis kode ini sendiri daripada menggunakan std::remove?
Jerry Coffin

Dan apakah iterator pada std :: list masih berlaku untuk kenaikan setelah elemen yang dirujuknya dihapus?
Steve Jessop

3
Saya hanya ingin menghapus 1 elemen, jadi 'break;', menggunakan 'remove' akan menghilangkan semua pertandingan yang membutuhkan waktu lebih lama dan tidak melakukan apa yang saya inginkan. Elemen yang ingin saya hapus dalam kasus khusus ini akan hampir selalu menjadi akhir dari daftar atau sangat dekat dengannya, sehingga iterating secara terbalik juga lebih cepat dan lebih cocok untuk masalah tersebut.
0xC0DEFACE

4
stackoverflow.com/a/2160581/12386 Ini mengatakan para desainer secara khusus tidak mendefinisikan implementasi karena Anda sebagai pengguna tidak seharusnya tahu atau peduli, namun @seh di atas mengharapkan kami untuk secara ajaib hanya mengetahui rend () dihitung dan mahal.
stu

Jawaban:


181

Setelah beberapa penelitian dan pengujian saya menemukan solusinya. Rupanya sesuai dengan standar [24.4.1 / 1] hubungan antara i.base () dan i adalah:

&*(reverse_iterator(i)) == &*(i - 1)

(dari artikel Dr. Dobbs ):

teks alternatif

Jadi, Anda perlu menerapkan offset saat mendapatkan basis (). Karena itu solusinya adalah:

m_CursorStack.erase( --(i.base()) );

EDIT

Memperbarui untuk C ++ 11.

reverse_iterator itidak berubah:

m_CursorStack.erase( std::next(i).base() );

reverse_iterator icanggih:

std::advance(i, 1);
m_CursorStack.erase( i.base() );

Saya menemukan ini jauh lebih jelas daripada solusi saya sebelumnya. Gunakan mana pun yang Anda butuhkan.


27
Anda harus mencatat sedikit lebih banyak dari artikel yang Anda kutip - untuk menjadi portabel ekspresi harus m_CursorStack.erase( (++i).base())(man, melakukan hal ini dengan iterator terbalik membuat kepala saya sakit ...). Juga harus dicatat bahwa artikel DDJ dimasukkan ke dalam buku "Efektif STL" Meyer.
Michael Burr

8
Saya menemukan diagram itu lebih membingungkan daripada membantu. Karena rbegin, ri dan rend semuanya benar - benar menunjuk elemen di sebelah kanan dari apa yang mereka maksudkan . Diagram menunjukkan elemen apa yang akan Anda akses jika Anda *, tetapi kita sedang berbicara tentang elemen apa yang akan Anda tunjukkan jika Anda base, yang merupakan satu elemen di sebelah kanan. Saya bukan penggemar --(i.base())atau (++i).base()solusi karena mereka bermutasi iterator. Saya lebih suka (i+1).base()yang bekerja juga.
mgiuca

4
Reverse iterator adalah pembohong .. saat ditangguhkan, iterator terbalik mengembalikan elemen sebelumnya . Lihat di sini
bobobobo

4
Hanya untuk benar-benar jelas, teknik ini masih tidak dapat digunakan dalam loop normal (di mana iterator bertambah secara normal). Lihat stackoverflow.com/questions/37005449/…
logidelic

1
m_CursorStack.erase ((++ i) .base ()) sepertinya akan menjadi masalah jika ++ saya membuat Anda melewati elemen terakhir. dapatkah Anda memanggil hapus di akhir ()?
stu

16

Harap dicatat bahwa m_CursorStack.erase( (++i).base())mungkin ada masalah jika digunakan dalam satu forlingkaran (lihat pertanyaan asli) karena itu mengubah nilai i. Ekspresi yang benar adalahm_CursorStack.erase((i+1).base())


4
Anda harus membuat salinan iterator dan lakukan iterator j = i ; ++j, karena i+1tidak bekerja pada iterator, tapi itu ide yang tepat
bobobobo

3
@obobobo, Anda dapat menggunakan m_CursorStack.erase(boost::next(i).base())dengan Boost. atau di C ++ 11m_CursorStack.erase(std::next(i).base())
alfC

12

... atau cara lain untuk menghapus elemen ini dari daftar?

Ini memerlukan -std=c++11bendera (untuk auto):

auto it=vt.end();
while (it>vt.begin())
{
    it--;
    if (*it == pCursor) //{ delete *it;
        it = vt.erase(it); //}
}

Berhasil :)
Den-Jason

@ GaetanoMendola: mengapa?
slashmais

3
Siapa yang menjamin Anda bahwa daftar iterator diurutkan?
Gaetano Mendola

7

Lucu bahwa belum ada solusi yang benar di halaman ini. Jadi, berikut ini yang benar:

Dalam hal iterator maju solusinya lurus ke depan:

std::list< int >::iterator i = myList.begin();
while ( ; i != myList.end(); ) {
  if ( *i == to_delete ) {
    i = myList.erase( i );
  } else {
    ++i;
  } 
}

Dalam hal iterator terbalik, Anda perlu melakukan hal yang sama:

std::list< int >::reverse_iterator i = myList.rbegin();
while ( ; i != myList.rend(); ) {
  if ( *i == to_delete ) {
    i = decltype(i)(myList.erase( std::next(i).base() ));
  } else {
    ++i;
  } 
}

Catatan:

  • Anda dapat membangun reverse_iteratordari iterator
  • Anda dapat menggunakan nilai balik dari std::list::erase

Kode ini berfungsi, tetapi tolong jelaskan mengapa selanjutnya digunakan dan bagaimana aman untuk melemparkan iterator maju ke iterator terbalik tanpa dunia runtuh
Lefteris E

1
@LefterisE Itu bukan pemain. Ini menciptakan iterator terbalik baru dari interator. Ini adalah konstruktor normal dari iterator terbalik.
Šimon Tóth

3

Saat menggunakan metode reverse_iterator's base()dan mengurangi hasilnya berfungsi di sini, perlu dicatat bahwa' reverse_iterators tidak diberi status yang sama dengan ' iterators biasa . Secara umum, Anda harus memilih iterators biasa reverse_iterator( const_iteratordan const_reverse_iterators dan s), untuk alasan yang tepat seperti ini. Lihat Jurnal Dokter Dobbs untuk pembahasan mendalam tentang mengapa.


3
typedef std::map<size_t, some_class*> TMap;
TMap Map;
.......

for( TMap::const_reverse_iterator It = Map.rbegin(), end = Map.rend(); It != end; It++ )
{
    TMap::const_iterator Obsolete = It.base();   // conversion into const_iterator
    It++;
    Map.erase( Obsolete );
    It--;
}

3

Dan di sini adalah potongan kode untuk mengubah hasil hapus kembali ke iterator terbalik untuk menghapus elemen dalam wadah saat iterasi terbalik. Agak aneh, tetapi berfungsi bahkan saat menghapus elemen pertama atau terakhir:

std::set<int> set{1,2,3,4,5};

for (auto itr = set.rbegin(); itr != set.rend(); )
{    
    if (*itr == 3)
    {
        auto it = set.erase(--itr.base());
        itr = std::reverse_iterator(it);            
    }
    else
        ++itr;
}

2

Jika Anda tidak perlu menghapus semuanya saat Anda melanjutkan, maka untuk menyelesaikan masalah, Anda dapat menggunakan idiom hapus-hapus:

m_CursorStack.erase(std::remove(m_CursorStack.begin(), m_CursorStack.end(), pCursor), m_CursorStack.end());

std::removemenukar semua item dalam wadah yang cocok pCursorsampai akhir, dan mengembalikan iterator ke item pertama yang cocok. Kemudian, rentang erasemenggunakan akan dihapus dari pertandingan pertama, dan pergi ke akhir. Urutan elemen yang tidak cocok dipertahankan.

Ini mungkin bekerja lebih cepat untuk Anda jika Anda menggunakan std::vector, di mana menghapus di tengah konten dapat melibatkan banyak menyalin atau memindahkan.

Atau tentu saja, jawaban di atas menjelaskan penggunaan reverse_iterator::base()yang menarik dan layak diketahui, untuk memecahkan masalah yang dinyatakan, saya berpendapat bahwa std::removeini lebih cocok.


1

Hanya ingin mengklarifikasi sesuatu: Dalam beberapa komentar di atas dan menjawab versi portable untuk erase disebut sebagai (++ i) .base (). Namun, kecuali jika saya melewatkan sesuatu, pernyataan yang benar adalah (++ ri) .base (), artinya Anda 'menambah' the reverse_iterator (bukan iterator).

Saya bertemu dengan kebutuhan untuk melakukan sesuatu yang serupa kemarin dan posting ini sangat membantu. Terimakasih semuanya.


0

Untuk melengkapi jawaban orang lain dan karena saya menemukan pertanyaan ini ketika mencari tentang std :: string tanpa banyak keberhasilan, ini dia tanggapan dengan penggunaan std :: string, std :: string :: erase dan std :: reverse_iterator

Masalah saya adalah menghapus nama file gambar dari string nama file lengkap. Awalnya diselesaikan dengan std :: string :: find_last_of, namun saya mencari cara alternatif dengan std :: reverse_iterator.

std::string haystack("\\\\UNC\\complete\\file\\path.exe");
auto&& it = std::find_if( std::rbegin(haystack), std::rend(haystack), []( char ch){ return ch == '\\'; } );
auto&& it2 = std::string::iterator( std::begin( haystack ) + std::distance(it, std::rend(haystack)) );
haystack.erase(it2, std::end(haystack));
std::cout << haystack;  ////// prints: '\\UNC\complete\file\'

Ini menggunakan algoritma, iterator, dan header string.


0

iterator terbalik cukup sulit digunakan. Jadi hanya digunakan iterator umum. Mulai dari elemen terakhir. Ketika menemukan sesuatu untuk dihapus. hapus dan kembalikan iterator berikutnya. misal ketika menghapus elemen ke-3 akan menunjuk elemen ke-4 saat ini. dan baru ke-3. Jadi harus dikurangi 1 untuk bergerak ke kiri

void remchar(string& s,char c)
{      
    auto r = s.end() - 1;
    while (r >= s.begin() && *r == c)
    {
        r = s.erase(r);
        r -= 1;
    }
}
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.