ทำไม `std :: string :: find ()` ไม่ส่งคืนตัววนซ้ำสุดท้ายเมื่อความล้มเหลว?


29

ฉันพบว่าพฤติกรรมของstd::string::findไม่สอดคล้องกับคอนเทนเนอร์ C ++ มาตรฐาน

เช่น

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

แต่สำหรับสตริง

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

ทำไมไม่ควรล้มเหลวในmyStr.find('!')การกลับมาmyStr.end()แทนstd::string::npos?

เนื่องจากstd::stringค่อนข้างจะพิเศษเมื่อเทียบกับตู้คอนเทนเนอร์อื่น ๆ ฉันสงสัยว่ามีเหตุผลจริง ๆ อยู่เบื้องหลังสิ่งนี้หรือไม่ (น่าแปลกที่ฉันไม่พบใครซักคนที่นี่)


5
ฉันคิดว่าคำตอบที่สมเหตุสมผลเท่านั้นใกล้เคียงกับคำตอบสำหรับคำถาม: 'ทำไมฮอทดอกถึงเต็มใน 4 และฮอทด็อกบันสใน 6' ดีก็คือวิธีที่ happend โลกจะเป็น
bartop

ตรวจสอบนี้
NutCracker

IMHO เหตุผลสำหรับพฤติกรรมนี้คือstd::stringภายในประกอบด้วยอักขระที่มีองค์ประกอบราคาไม่แพง (ในเรื่องของหน่วยความจำ) และยิ่งไปกว่านั้นตัวละครเป็นประเภทเดียวที่std::stringสามารถมี ในทางกลับกันstd::mapประกอบด้วยองค์ประกอบที่ซับซ้อนมากขึ้น นอกจากนี้สเปคของstd::map::findบอกว่ามันควรจะหาองค์ประกอบและสเปคของstd::string::findบอกว่ามันเป็นงานที่จะหาตำแหน่ง
NutCracker

สำหรับแผนที่คุณไม่สามารถมีตัววนซ้ำ npos เพื่อใช้ตัววนซ้ำสุดท้าย สำหรับสตริงเราสามารถใช้ npos ได้แล้วทำไมไม่เป็นเช่นนั้น :)
LF

คำตอบ:


28

เริ่มต้นด้วยstd::stringอินเทอร์เฟซที่รู้จักกันดีว่าบวมและไม่สอดคล้องกันให้ดูที่Gotw84ของ Herb Sutter ในหัวข้อนี้ แต่ยังคงมีเหตุผลที่อยู่เบื้องหลังกลับดัชนี:std::string::find std::string::substrฟังก์ชั่นสมาชิกอำนวยความสะดวกนี้ทำงานกับดัชนีเช่น

const std::string src = "abcdefghijk";

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

คุณสามารถนำไปใช้substrเพื่อให้มันยอมรับตัววนซ้ำลงในสายอักขระ แต่จากนั้นเราไม่จำเป็นต้องรอนานสำหรับการร้องเรียนที่ดังมากซึ่งstd::stringไม่สามารถใช้งานได้ ดังนั้นเมื่อมีการstd::string::substrยอมรับดัชนีคุณจะหาดัชนีการเกิดขึ้นครั้งแรกของ'd'ในสายอักขระข้างต้นได้อย่างไรเพื่อพิมพ์ทุกอย่างที่เริ่มต้นจากสตริงย่อยนี้

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

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

นี่อาจไม่ใช่สิ่งที่คุณต้องการ ดังนั้นเราสามารถให้std::string::findผลตอบแทนดัชนีและที่นี่เรา:

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

หากคุณต้องการที่จะทำงานกับ iterators <algorithm>ใช้ พวกเขาอนุญาตให้คุณดังกล่าวข้างต้นเป็น

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

std::copy(it, src.cend(), std::ostream_iterator<char>(std::cout));

4
จุดดี. อย่างไรก็ตามแทนที่จะส่งคืนตัววนซ้ำstd::string::findสามารถยังคงกลับมาsize()ใช้แทนnposรักษาความเข้ากันได้กับsubstrในขณะที่ยังหลีกเลี่ยงการแสดงซ้ำหลายครั้ง
erenon

1
@erenon อาจ แต่std::string::substrครอบคลุมกรณี "เริ่มต้นที่นี่จนถึงตอนท้าย" ด้วยพารามิเตอร์เริ่มต้นสำหรับดัชนีที่สอง ( npos) ฉันเดาว่าการกลับมาsize()จะสร้างความสับสนและการมียามรักษาการณ์ตามตัวอักษรnposอาจเป็นทางเลือกที่ดีกว่า!
lubgr

@lubgr แต่ถ้าstd::string::findคืนค่าตัววนซ้ำstd::string::substrอาจจะยอมรับตัววนซ้ำสำหรับตำแหน่งเริ่มต้นด้วย ตัวอย่างของคุณที่มี find จะมีลักษณะเหมือนกันในทั้งสองกรณีในโลกทางเลือกนี้
Mattias Wallin

@MattiasWallin จุดดี แต่std::string::substrด้วยอาร์กิวเมนต์ตัววนซ้ำเปิดประตูสำหรับกรณี UB เพิ่มเติมอีกหนึ่งกรณี (นอกเหนือจากสถานการณ์ในอดีตที่สิ้นสุดที่สามารถเกิดขึ้นได้อย่างเท่าเทียมกันกับดัชนีหรือตัววนซ้ำ): ผ่านตัววนซ้ำที่อ้างถึงสตริงอื่น
lubgr

3

นี่เป็นเพราะstd::stringมีสองอินเตอร์เฟส:

  • อินเตอร์เฟสแบบวนซ้ำทั่วไปที่พบในคอนเทนเนอร์ทั้งหมด
  • std::stringเฉพาะดัชนีอินเตอร์เฟซ

std::string::findเป็นส่วนหนึ่งของอินเทอร์เฟซแบบอิงดัชนีดังนั้นจะส่งคืนดัชนี

ใช้std::findเพื่อใช้อินเตอร์เฟสที่ใช้ตัววนซ้ำทั่วไป

ใช้std::vector<char>ถ้าคุณไม่ต้องการอินเทอร์เฟซแบบอิงดัชนี (ไม่ต้องทำ)

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.