เหตุใดฟังก์ชัน <algorithm> ทั้งหมดจึงมีเพียงช่วงเท่านั้นไม่ใช่คอนเทนเนอร์?


49

มีฟังก์ชั่นที่มีประโยชน์มากมาย<algorithm>แต่ทุกฟังก์ชั่นทำงานใน"ลำดับ" - คู่ของตัววนซ้ำ เช่นถ้าฉันมีที่เก็บและต้องการที่จะใช้std::accumulateมันฉันต้องเขียน:

std::vector<int> myContainer = ...;
int sum = std::accumulate(myContainer.begin(), myContainer.end(), 0);

เมื่อทั้งหมดที่ฉันตั้งใจจะทำคือ:

int sum = std::accumulate(myContainer, 0);

ในสายตาของฉันซึ่งอ่านได้ชัดเจนขึ้น

ตอนนี้ฉันเห็นแล้วว่าอาจมีบางกรณีที่คุณต้องการใช้งานเฉพาะในส่วนของคอนเทนเนอร์ดังนั้นจึงมีประโยชน์อย่างมากที่จะมีตัวเลือกในการผ่านช่วง แต่อย่างน้อยในประสบการณ์ของฉันนั่นเป็นกรณีพิเศษที่หายาก ฉันมักจะต้องการที่จะทำงานบนภาชนะทั้งหมด

มันง่ายที่จะเขียนฟังก์ชั่น wrapper ซึ่งใช้คอนเทนเนอร์และการโทรbegin()และend()บนมัน แต่ฟังก์ชั่นความสะดวกสบายนั้นไม่รวมอยู่ในไลบรารี่มาตรฐาน

ฉันต้องการทราบเหตุผลที่อยู่เบื้องหลังตัวเลือกการออกแบบ STL นี้


7
โดยทั่วไปแล้ว STL จะให้บริการห่อหุ้มสิ่งอำนวยความสะดวกหรือไม่หรือเป็นไปตาม C ++ ที่เก่ากว่านโยบายต่อไปนี้คือเครื่องมือที่ใช้งานง่าย
Kilian Foth

2
สำหรับบันทึก: แทนที่จะเขียน wrapper ของคุณเองคุณควรใช้ algorithm wrappers ใน Boost.Range ในกรณีนี้boost::accumulate
ecatmur

คำตอบ:


40

... มันมีประโยชน์มากที่จะมีตัวเลือกในการผ่านช่วง แต่อย่างน้อยในประสบการณ์ของฉันนั่นเป็นกรณีพิเศษที่หายาก ฉันมักจะต้องการที่จะทำงานบนภาชนะทั้งหมด

อาจเป็นกรณีพิเศษที่หายากในประสบการณ์ของคุณแต่ในความเป็นจริงทั้งคอนเทนเนอร์ เป็นกรณีพิเศษและช่วงโดยพลการเป็นกรณีทั่วไป

คุณสังเกตเห็นแล้วว่าคุณสามารถใช้งานคอนเทนเนอร์ทั้งหมดได้โดยใช้อินเตอร์เฟสปัจจุบัน แต่คุณไม่สามารถสนทนาได้

ดังนั้นตัวเขียนไลบรารีจึงมีตัวเลือกระหว่างการนำอินเตอร์เฟสสองอินเตอร์เฟสไปด้านหน้าหรือใช้เฉพาะส่วนที่ยังครอบคลุมทุกกรณี


มันง่ายที่จะเขียนฟังก์ชั่น wrapper ซึ่งใช้คอนเทนเนอร์และการเริ่มต้น () และ end () บนมัน แต่ฟังก์ชั่นความสะดวกสบายดังกล่าวไม่รวมอยู่ในไลบรารีมาตรฐาน

จริงโดยเฉพาะอย่างยิ่งตั้งแต่ฟังก์ชั่นฟรีstd::beginและstd::endรวมอยู่ในขณะนี้

ดังนั้นสมมติว่าห้องสมุดให้ความสะดวกสบายเกินพิกัด:

template <typename Container>
void sort(Container &c) {
  sort(begin(c), end(c));
}

ตอนนี้มันยังต้องการให้เกินพิกัดเทียบเท่ากับการเปรียบเทียบ functor และเราจำเป็นต้องให้เทียบเท่าสำหรับอัลกอริทึมอื่น ๆ

แต่อย่างน้อยเราก็ครอบคลุมทุกกรณีที่เราต้องการที่จะทำงานบนที่เก็บเต็มใช่ไหม? ก็ไม่มาก พิจารณา

std::for_each(c.rbegin(), c.rend(), foo);

ถ้าเราต้องการจัดการกับการปฏิบัติการย้อนกลับบนคอนเทนเนอร์เราต้องการวิธีอื่น (หรือวิธีการคู่) ต่ออัลกอริทึมที่มีอยู่


ดังนั้นวิธีการที่อิงกับช่วงจึงกว้างกว่าในแง่ที่ง่าย ๆ ว่า:

  • มันสามารถทำทุกสิ่งได้ทั้งเวอร์ชั่นคอนเทนเนอร์
  • วิธีการทั้งคอนเทนเนอร์สองเท่าหรือสองเท่าจำนวนเกินพิกัดที่ต้องการในขณะที่ยังคงมีประสิทธิภาพน้อยลง
  • อัลกอริธึมที่อิงตามช่วงนั้นยังสามารถจัดเรียงได้ (คุณสามารถสแต็กหรืออะแด็ปเตอร์ตัววนซ้ำได้แม้ว่านี่จะเป็นภาษาที่ใช้งานได้จริงและ Python)

มีอีกเหตุผลที่ถูกต้องของหลักสูตรซึ่งเป็นว่ามันเป็นเรื่องอยู่แล้วเป็นจำนวนมากของการทำงานเพื่อให้ได้มาตรฐาน STL และพองด้วยความสะดวกสบายห่อก่อนที่มันจะได้รับการใช้กันอย่างแพร่หลายจะไม่ใช้งานที่ดีของเวลาคณะกรรมการ จำกัด หากคุณสนใจคุณสามารถดูรายงานทางเทคนิคของ Stepanov & Lee ได้ที่นี่

ตามที่ระบุไว้ในความคิดเห็นBoost.Rangeให้วิธีการที่ใหม่กว่าโดยไม่ต้องมีการเปลี่ยนแปลงมาตรฐาน


9
ฉันไม่คิดว่าใครรวม OP จะแนะนำให้เพิ่มการโอเวอร์โหลดสำหรับทุกกรณีพิเศษ แม้ว่า "ทั้งตู้คอนเทนเนอร์" เป็นเรื่องธรรมดาน้อยกว่า "ช่วงโดยพลการ" แต่มันก็พบได้ทั่วไปมากกว่า "ทั้งตู้คอนเทนเนอร์และกลับด้าน" จำกัด ไว้ที่f(c.begin(), c.end(), ...)และอาจเป็นเพียงโอเวอร์โหลดที่ใช้บ่อยที่สุด (แต่คุณกำหนดว่า) เพื่อป้องกันการเพิ่มจำนวนเกินพิกัดเป็นสองเท่า นอกจากนี้อะแดปเตอร์ตัววนซ้ำนั้นเป็นแบบมุมฉาก (ตามที่คุณทราบพวกมันทำงานได้ดีใน Python ซึ่งตัววนซ้ำทำงานแตกต่างกันมากและไม่มีอำนาจส่วนใหญ่ที่คุณพูดถึง)

3
ฉันเห็นด้วยทั้งคอนเทนเนอร์กรณีที่ส่งต่อเป็นเรื่องธรรมดามาก แต่ต้องการชี้ให้เห็นว่ามันเป็นชุดย่อย ๆ ของการใช้ที่เป็นไปได้น้อยกว่าคำถามที่แนะนำ เนื่องจากตัวเลือกไม่ได้อยู่ระหว่างคอนเทนเนอร์ทั้งหมดและคอนเทนเนอร์บางส่วน แต่ระหว่างคอนเทนเนอร์ทั้งหมดกับคอนเทนเนอร์บางส่วนอาจมีการย้อนกลับหรือดัดแปลงเป็นอย่างอื่น และฉันคิดว่ามันยุติธรรมที่จะแนะนำว่าความซับซ้อนของการใช้อะแดปเตอร์ที่รับรู้มากขึ้นถ้าคุณต้องเปลี่ยนอัลกอริทึมของคุณมากเกินไป
ไร้ประโยชน์

23
หมายเหตุรุ่นคอนเทนเนอร์จะครอบคลุมทุกกรณีถ้า STL เสนอวัตถุช่วง std::sort(std::range(start, stop))เช่น

3
ในทางตรงกันข้าม: อัลกอริธึมการทำงานที่สามารถคอมโพสิตได้ (เช่นแผนที่และตัวกรอง) ใช้วัตถุเดียวที่แสดงถึงการรวบรวมและส่งคืนวัตถุเดียวแน่นอนว่าพวกเขาไม่ได้ใช้อะไรเลยที่คล้ายกับตัววนซ้ำคู่หนึ่ง
svick

3
มาโครสามารถทำสิ่งนี้ได้: #define MAKE_RANGE(container) (container).begin(), (container).end()</jk>
ratchet freak

21

ปรากฎว่ามีบทความโดย Herb Sutterในหัวข้อนี้ โดยทั่วไปปัญหาคือความคลุมเครือเกินพิกัด รับดังต่อไปนี้:

template<typename Iter>
void sort( Iter, Iter ); // 1

template<typename Iter, typename Pred>
void sort( Iter, Iter, Pred ); // 2

และเพิ่มสิ่งต่อไปนี้:

template<typename Container>
void sort( Container& ); // 3

template<typename Container, typename Pred>
void sort( Container&, Pred ); // 4

จะทำให้แยกแยะได้ยาก4และ1เหมาะสม

แนวคิดที่เสนอ แต่ในท้ายที่สุดไม่ได้รวมอยู่ใน C ++ 0x enable_ifจะมีการแก้ไขนั้นและมันยังเป็นไปได้ที่จะหลีกเลี่ยงได้โดยใช้ สำหรับอัลกอริทึมบางตัวก็ไม่มีปัญหาอะไรเลย แต่พวกเขาตัดสินใจต่อต้านมัน

ตอนนี้หลังจากอ่านความคิดเห็นและคำตอบทั้งหมดที่นี่ฉันคิดว่าrangeวัตถุจะเป็นทางออกที่ดีที่สุด Boost.Rangeฉันคิดว่าฉันจะมีลักษณะที่


1
การใช้ภาษาที่typename Iterดูเหมือนจะเป็นเป็ดเกินไปสำหรับภาษาที่เข้มงวด ฉันชอบเช่นtemplate<typename Container> void sort(typename Container::iterator, typename Container::iterator); // 1และtemplate<template<class> Container, typename T> void sort( Container<T>&, std::function<bool(const T&)> ); // 4ฯลฯ (ซึ่งอาจจะแก้ปัญหาความคลุมเครือ)
ลาด

@Vlad: น่าเสียดายที่นี่ไม่สามารถใช้ได้กับอาร์เรย์แบบเก่าธรรมดาเนื่องจากไม่มีT[]::iteratorอยู่ นอกจากนี้ตัวทำซ้ำที่เหมาะสมไม่จำเป็นต้องเป็นคอลเลกชันประเภทใด ๆ ที่ซ้อนกันเพียงพอที่จะกำหนดstd::iterator_traitsได้
firegurafiku

@firegurafiku: ดีอาร์เรย์เป็นเรื่องง่ายกรณีพิเศษด้วยเทคนิค TMP ขั้นพื้นฐานบางอย่าง
Vlad

11

โดยทั่วไปการตัดสินใจแบบดั้งเดิม แนวคิดตัววนซ้ำถูกสร้างแบบจำลองบนพอยน์เตอร์ แต่คอนเทนเนอร์ไม่ได้จำลองแบบบนอาร์เรย์ นอกจากนี้เนื่องจากอาร์เรย์ยากที่จะผ่าน (ต้องการพารามิเตอร์เทมเพลตที่ไม่มีความยาวโดยทั่วไป) จึงมักจะมีเพียงฟังก์ชันที่มีพอยน์เตอร์เท่านั้น

แต่ใช่แล้วในการเข้าใจย้อนหลังการตัดสินใจผิด เราก็จะได้รับดีกว่าออกด้วย constructible ช่วงวัตถุจากทั้งสองbegin/endหรือbegin/length; ตอนนี้เรามี_nอัลกอริทึมต่อท้ายหลายแทน


5

การเพิ่มพวกมันจะทำให้คุณไม่มีอำนาจ (คุณสามารถทำทั้งคอนเทนเนอร์โดยการโทร.begin()และ.end()ตัวคุณเอง) และมันจะเพิ่มสิ่งหนึ่งไปยังไลบรารีที่ต้องระบุอย่างถูกต้องเพิ่มไปยังไลบรารีโดยผู้ขายทดสอบบำรุงรักษา ฯลฯ

กล่าวโดยสรุปก็อาจไม่ได้อยู่ที่นั่นเพราะมันไม่คุ้มค่ากับปัญหาในการดูแลชุดเทมเพลตเพิ่มเติมเพียงเพื่อช่วยให้ผู้ใช้ที่เก็บทั้งตู้ไม่สามารถพิมพ์พารามิเตอร์การเรียกฟังก์ชันพิเศษหนึ่งรายการ


9
มันจะไม่ทำให้ฉันมีพลังอำนาจนั่นเป็นเรื่องจริง - แต่ในที่สุดแล้วก็ไม่std::getlineได้และยังอยู่ในห้องสมุด หนึ่งอาจไปไกลที่สุดเท่าที่จะบอกว่าโครงสร้างการควบคุมการขยายไม่ได้รับฉันอำนาจที่ผมจะทำทุกอย่างโดยใช้เพียงและif gotoใช่การเปรียบเทียบที่ไม่เป็นธรรมฉันรู้;) ฉันคิดว่าฉันสามารถเข้าใจสเปค / การใช้งาน / การบำรุงรักษาอย่างใด แต่มันเป็นเพียงเสื้อคลุมตัวเล็ก ๆ ที่เราพูดถึงที่นี่ดังนั้น ..
กีตาร์ตาย

เสื้อคลุมเล็ก ๆ ไม่มีค่าใช้จ่ายในการเขียนโค้ดและอาจไม่มีเหตุผลใด ๆ ที่จะต้องอยู่ในห้องสมุด
ebasconp

-1

โดยขณะนี้http://en.wikipedia.org/wiki/C++11#Range-based_for_loopstd::for_eachเป็นทางเลือกที่ดีที่จะ สังเกตไม่มีตัววนซ้ำที่ชัดเจน:

int a[5] = {1, 2, 3, 4, 5};
for (auto &i: a) { i *= 2; }

(ได้รับแรงบันดาลใจจากhttps://stackoverflow.com/a/694534/2097284 )


1
มันแก้ได้เพียงส่วนเดียว<algorithm>เท่านั้นไม่ใช่อัลกอสจริงทั้งหมดที่ต้องการbeginและตัวendวนซ้ำ - แต่ประโยชน์ไม่สามารถคุยโวได้! เมื่อฉันลอง C ++ 03 ครั้งแรกในปี 2009ish ฉันแยกตัวออกจากตัววนซ้ำเนื่องจากแผงวงจรวนซ้ำและโชคดีหรือไม่โครงการของฉันในเวลานั้นอนุญาต เริ่มต้นใหม่ใน C ++ 11 ในปี 2014 มันเป็นการอัพเกรดที่เหลือเชื่อภาษา C ++ ที่ควรจะเป็นเสมอและตอนนี้ฉันไม่สามารถอยู่ได้โดยปราศจากauto &it: them:)
underscore_d
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.