Mengapa `std :: string :: find ()` tidak mengembalikan iterator akhir saat gagal?

29

Saya menemukan perilaku std::string::findtidak konsisten dengan wadah standar C ++.

Misalnya

std::map<int, int> myMap = {{1, 2}};
auto it = myMap.find(10);  // it == myMap.end()

Tapi untuk sebuah string,

std::string myStr = "hello";
auto it = myStr.find('!');  // it == std::string::npos

Kenapa bukan yang gagal myStr.find('!')kembali myStr.end()saja std::string::npos?

Karena std::stringagak istimewa jika dibandingkan dengan wadah lain, saya bertanya-tanya apakah ada alasan nyata di balik ini. (Anehnya, saya tidak dapat menemukan orang yang mempertanyakan hal ini di mana pun).

Sumudu
sumber
5
Saya pikir hanya jawaban yang masuk akal yang menjawab pertanyaan: 'Mengapa hotdog dikemas dalam 4 dan roti hotdog dalam 6?' Nah, begitulah dunia menjadi
bahagia
Lihat ini
NutCracker
IMHO, alasan perilaku ini adalah bahwa secara std::stringinternal terdiri dari karakter yang merupakan elemen murah (dalam hal memori). Dan, lebih jauh lagi, karakter adalah satu-satunya tipe yang std::stringbisa mengandung. Di sisi lain, std::mapterdiri dari elemen yang lebih kompleks. Juga, spesifikasi std::map::findmengatakan bahwa ia seharusnya menemukan elemen, dan spesifikasi std::string::findmengatakan bahwa tugasnya adalah menemukan posisi.
NutCracker
Untuk peta, Anda tidak dapat memiliki npos iterator sehingga iterator akhir digunakan. Untuk string, kita bisa menggunakan npos, jadi kenapa tidak :)
LF

Jawaban:

28

Untuk memulainya, std::stringantarmuka dikenal membengkak dan tidak konsisten, lihat Herb Sutter's Gotw84 tentang topik ini. Namun demikian, ada alasan di balik std::string::findkembali indeks: std::string::substr. Fungsi kenyamanan anggota ini beroperasi pada indeks, misalnya

const std::string src = "abcdefghijk";

std::cout << src.substr(2, 5) << "\n";

Anda dapat mengimplementasikan substrsedemikian rupa sehingga menerima iterator ke dalam string, tetapi kemudian kita tidak perlu menunggu lama untuk keluhan keras yang std::stringtidak dapat digunakan dan berlawanan dengan intuisi. Jadi mengingat bahwa std::string::substrmenerima indeks, bagaimana Anda menemukan indeks kemunculan pertama 'd'dalam string input di atas untuk mencetak semuanya mulai dari substring ini?

const auto it = src.find('d'); // imagine this returns an iterator

std::cout << src.substr(std::distance(src.cbegin(), it));

Ini mungkin juga bukan yang Anda inginkan. Karenanya kita dapat membiarkan std::string::findmengembalikan indeks, dan di sini kita:

const std::string extracted = src.substr(src.find('d'));

Jika Anda ingin bekerja dengan iterator, gunakan <algorithm>. Mereka memungkinkan Anda untuk di atas sebagai

auto it = std::find(src.cbegin(), src.cend(), 'd');

std::copy(it, src.cend(), std::ostream_iterator<char>(std::cout));
lubgr
sumber
4
Poin bagus. Namun, alih-alih mengembalikan iterator, std::string::findmasih bisa mengembalikan size(), alih-alih npos, mempertahankan kompatibilitas dengan substr, sambil juga menghindari beberapa bilah ekstra.
erenon
1
@erenon Mungkin, tetapi std::string::substrsudah mencakup kasus "mulai di sini sampai akhir" dengan parameter default untuk indeks kedua ( npos). Saya kira kembali size()juga akan membingungkan dan memiliki penjaga seperti itu nposmungkin merupakan pilihan yang lebih baik?
lubgr
@ lubgr Tetapi jika std::string::findmengembalikan iterator, std::string::substrmungkin juga akan menerima iterator untuk posisi awal. Contoh Anda dengan find akan terlihat sama dalam kedua kasus di dunia alternatif ini.
Mattias Wallin
@MattiasWallin Poin bagus. Tetapi std::string::substrdengan argumen iterator membuka pintu untuk satu kasus UB lebih lanjut (selain skenario masa lalu-akhir yang bisa sama baiknya dengan indeks atau iterator): melewati iterator yang merujuk ke string lain.
lubgr
3

Ini karena std::stringmemiliki dua antarmuka:

  • Antarmuka berbasis iterator umum ditemukan pada semua wadah
  • The std::stringspesifik Indeks antarmuka berbasis

std::string::findadalah bagian dari antarmuka berbasis indeks , dan karenanya mengembalikan indeks.

Gunakan std::finduntuk menggunakan antarmuka berbasis iterator umum.

Gunakan std::vector<char>jika Anda tidak ingin antarmuka berbasis indeks (jangan lakukan ini).

Mattias Wallin
sumber