C ++ Iterator ทำไมไม่มีคลาสพื้นฐานของ Iterator ตัววนซ้ำทั้งหมดที่สืบทอดมา


11

ฉันกำลังเรียนเพื่อสอบและฉันมีคำถามที่ฉันต้องดิ้นรนเพื่อให้และตอบคำถาม

ทำไมคลาสพื้นฐานของตัววนซ้ำไม่มีอยู่ตัววนซ้ำอื่น ๆ ทั้งหมดจึงสืบทอดมา?

ฉันเดาว่าครูของฉันกำลังอ้างถึงโครงสร้างลำดับชั้นจากการอ้างอิง cpp " http://prntscr.com/mgj542 " และเราต้องให้เหตุผลอื่นนอกเหนือจากเหตุผลที่ควรทำ

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

พวกเขาอาจเป็นแม่แบบพิเศษขึ้นอยู่กับคอนเทนเนอร์ใช่ไหม?


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

5
" ทำไมคลาสพื้นฐานของตัววนซ้ำไม่มีตัววนซ้ำอื่น ๆ ทั้งหมดสืบทอดมาจากไหน " อืม ... ทำไมต้องมีอย่างนั้น?
Nicol Bolas

คำตอบ:


14

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

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

ลองพิจารณาตัวอย่างเช่นลำดับชั้นตัววนซ้ำแบบง่ายที่ใช้การสืบทอดและฟังก์ชันเสมือน:

template <class T>
class iterator_base { 
public:
    virtual T &operator*() = 0;
    virtual iterator_base &operator++() = 0;
    virtual bool operator==(iterator_base const &other) { return pos == other.pos; }
    virtual bool operator!=(iterator_base const &other) { return pos != other.pos; }
    iterator_base(T *pos) : pos(pos) {}
protected:
    T *pos;
};

template <class T>
class array_iterator : public iterator_base<T> {
public: 
    virtual T &operator*() override { return *pos; }
    virtual array_iterator &operator++() override { ++pos; return *this; }
    array_iterator(T *pos) : iterator_base(pos) {}
};

จากนั้นให้ทดสอบอย่างรวดเร็ว:

int main() { 
    char input[] = "asdfasdfasdfasdfasdfasdfasdfadsfasdqwerqwerqwerqrwertytyuiyuoiiuoThis is a stringy to search for something";
    using namespace std::chrono;

    auto start1 = high_resolution_clock::now();
    auto pos = std::find(std::begin(input), std::end(input), 'g');
    auto stop1 = high_resolution_clock::now();

    std::cout << *++pos << "\n";

    auto start2 = high_resolution_clock::now();
    auto pos2 = std::find(array_iterator(input), array_iterator(input+sizeof(input)), 'g');
    auto stop2 = high_resolution_clock::now();

    std::cout << *++pos2 << "\n";

    std::cout << "time1: " << duration_cast<nanoseconds>(stop1 - start1).count() << "ns\n";
    std::cout << "time2: " << duration_cast<nanoseconds>(stop2 - start2).count() << "ns\n";
}

[หมายเหตุ: ขึ้นอยู่กับคอมไพเลอร์ของคุณคุณอาจต้องทำเพิ่มอีกเล็กน้อยเช่นการกำหนด iterator_category, different_type การอ้างอิงและอื่น ๆ เพื่อให้คอมไพเลอร์ยอมรับ iterator]

และผลลัพธ์คือ:

y
y
time1: 1833ns
time2: 2933ns

[แน่นอนถ้าคุณเรียกใช้รหัสผลลัพธ์ของคุณจะไม่ตรงกับสิ่งเหล่านี้ทั้งหมด]

ดังนั้นแม้สำหรับกรณีง่าย ๆ นี้ (และเพิ่มขึ้นเพียงประมาณ 80 ครั้งและเปรียบเทียบ) เราได้เพิ่มค่าใช้จ่ายประมาณ 60% ในการค้นหาแบบเส้นตรง โดยเฉพาะอย่างยิ่งเมื่อมีการเพิ่มตัววนซ้ำลงใน C ++ แต่บางคนก็ไม่ยอมรับการออกแบบที่มีค่าใช้จ่ายมาก พวกเขาอาจจะไม่ได้รับมาตรฐานและแม้ว่าพวกเขาจะมีไม่มีใครจะใช้พวกเขา


7

ความแตกต่างคือระหว่างอะไรบางสิ่งบางอย่างและวิธีการสิ่งที่จะทำงาน

มีหลายภาษาที่พยายามทำให้ทั้งสองภาษามารวมกัน แต่พวกมันค่อนข้างชัดเจน

ถ้าเป็นอย่างไรและอะไรคือ ...

หากทุกอย่างสืบทอดมาจากobjectนั้นผลประโยชน์บางอย่างที่เกิดขึ้นเช่น: ตัวแปรของวัตถุใด ๆ สามารถเก็บค่าใด ๆ ที่เคย แต่นั่นก็คือการถูทุกอย่างจะต้องประพฤติ ( อย่างไร ) เหมือนobjectและดูเหมือน ( อะไร ) objectและ

แต่:

  • เกิดอะไรขึ้นถ้าวัตถุของคุณไม่มีคำจำกัดความที่มีความหมายของความเท่าเทียมกัน
  • เกิดอะไรขึ้นถ้ามันไม่มีแฮชที่สื่อความหมาย?
  • ถ้าวัตถุของคุณไม่สามารถโคลนได้ แต่วัตถุสามารถเป็นได้

ทั้งobjectประเภทกลายเป็นไร้ประโยชน์เป็นหลัก - เนื่องจากวัตถุที่ให้ความธรรมดาในทุกกรณีที่เป็นไปได้ หรือจะมีวัตถุที่มีคำจำกัดความแตกหัก / รองเท้า - เขา / ไร้สาระของคุณสมบัติสากลบางอย่างที่สันนิษฐานว่าพบobjectซึ่งพิสูจน์พฤติกรรมเกือบสากลยกเว้นสำหรับ gotchas จำนวนมาก

หากมีข้อผูกมัดอย่างไรบ้าง

อีกวิธีหนึ่งคุณสามารถเก็บอะไรและวิธีแยก จากนั้นหลายประเภทที่แตกต่างกัน (มีอะไรที่เหมือนกันในทุกสิ่ง ) สามารถประพฤติในทางเดียวกันเท่าที่เห็นจากผู้ร่วมงานว่า ในแง่นี้ความคิดของนั้นIteratorไม่ได้เป็นเฉพาะสิ่งที่แต่วิธีการ โดยเฉพาะวิธีการทำคุณโต้ตอบกับสิ่งที่คุณยังไม่ทราบว่าสิ่งที่คุณกำลังมีปฏิสัมพันธ์กับ

Java (และที่คล้ายกัน) อนุญาตวิธีการนี้โดยใช้อินเตอร์เฟส อินเทอร์เฟซในเรื่องนี้อธิบายวิธีการสื่อสารและโดยปริยายโปรโตคอลการสื่อสารและการกระทำที่ตามมา สิ่งใดก็ตามที่ประกาศว่าตัวเองเป็นของวิธีการที่ระบุระบุว่ามันสนับสนุนการสื่อสารที่เกี่ยวข้องและการกระทำที่ระบุไว้โดยโปรโตคอล นี้จะช่วยให้ผู้ร่วมงานใด ๆ ที่จะพึ่งพาวิธีและไม่ได้รับจมลงโดยระบุว่าที่อะไร 's สามารถนำมาใช้

C ++ (และที่คล้ายกัน) อนุญาตวิธีการนี้โดยการพิมพ์เป็ด เท็มเพลตไม่สนใจว่าประเภทการทำงานร่วมกันประกาศว่ามันเป็นไปตามพฤติกรรมเพียงแค่ว่าในบริบทการรวบรวมที่กำหนดว่าวัตถุสามารถโต้ตอบกับในลักษณะที่เฉพาะเจาะจง สิ่งนี้อนุญาตให้พอยน์เตอร์ C ++ และโอเปอเรเตอร์การโอเวอร์เฮดของอ็อปเจ็กใช้รหัสเดียวกันได้ เพราะตรงตามรายการตรวจสอบเพื่อพิจารณาเทียบเท่า

  • รองรับ * a, a->, ++ a และ a ++ -> iterator อินพุต / ฟอร์เวิร์ด
  • รองรับ * a, a->, ++ a, a ++, --a และ a-- -> iterator แบบสองทิศทาง

ประเภทพื้นฐานไม่จำเป็นต้องวนซ้ำคอนเทนเนอร์มันอาจเป็นอะไรก็ได้ ยิ่งไปกว่านั้นมันช่วยให้ผู้ทำงานร่วมกันบางคนเป็นสามัญมากขึ้นจินตนาการถึงความต้องการฟังก์ชั่นเท่านั้นa++ตัววนซ้ำสามารถตอบสนองได้ดังนั้นตัวชี้จึงสามารถเป็นจำนวนเต็มดังนั้นวัตถุใด ๆ ที่สามารถนำไปปฏิบัติoperator++ได้

ภายใต้และสูงกว่าข้อกำหนด

ปัญหาเกี่ยวกับวิธีการทั้งสองอยู่ภายใต้และเกินสเปค

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

ใช้แม่แบบต้องการให้ผลงานที่ทำงานร่วมกันกับที่ไม่รู้จักสมบูรณ์อะไรและผ่านการมีปฏิสัมพันธ์ของมันกำหนดวิธี ที่มีขอบเขตนี้จะทำให้การเขียนทำงานร่วมกันหนักขึ้นในขณะที่มันจะต้องวิเคราะห์อะไรสำหรับวิทยาการสื่อสาร (ฟังก์ชั่น / เขต / ฯลฯ ) ขณะที่หลีกเลี่ยงข้อผิดพลาดการรวบรวมหรือที่จุดน้อยวิธีการที่กำหนดสิ่งที่ไม่ตรงกับความต้องการของสำหรับวิธี นี้จะช่วยให้ผู้ทำงานร่วมกันที่จะต้องน้อยที่สุดจากการใดก็ตามสิ่งที่ช่วยให้ช่วงกว้างของสิ่งที่เป็นที่จะใช้ น่าเสียดายที่นี่มีข้อเสียของการอนุญาตการใช้วัตถุที่ไร้สาระซึ่งทางเทคนิคให้การสื่อสารพื้นฐานสำหรับสิ่งที่กำหนดอย่างไรแต่ไม่ปฏิบัติตามโปรโตคอลโดยนัยทำให้สิ่งเลวร้ายทุกประเภทเกิดขึ้น

iterators

ในกรณีนี้Iteratorจะเป็นวิธีย่อสำหรับคำอธิบายของการโต้ตอบ สิ่งใดที่ตรงกับคำอธิบายนั้นเป็นคำจำกัดความIteratorและ รู้วิธีช่วยให้เราสามารถเขียนขั้นตอนวิธีการทั่วไปและมีรายชื่อของ ' วิธี ' s รับเฉพาะสิ่งที่ว่าจะต้องมีการระบุไว้ในการที่จะทำให้การทำงานของอัลกอริทึม รายการนั่นคือฟังก์ชัน / properties / etc การใช้งานของพวกเขาคำนึงถึงเฉพาะสิ่งที่ถูกจัดการโดยอัลกอริทึม


6

เนื่องจาก C ++ ไม่จำเป็นต้องมีคลาสพื้นฐาน (นามธรรม) เพื่อทำ polymorphism มันมีsubtyping โครงสร้างเช่นเดียวกับsubtyping ประโยค

ในกรณีเฉพาะของ Iterators ที่สับสนสับสนมาตรฐานก่อนหน้านี้กำหนดไว้std::iterator(โดยประมาณ)

template <class Category, class T, class Distance = std::ptrdiff_t, class Pointer = T*, class Reference = T&>
struct iterator {
    using iterator_category = Category;
    using value_type = T;
    using difference_type = Distance;
    using pointer = Pointer;
    using reference = Reference;
}

เช่นเป็นเพียงผู้ให้บริการประเภทสมาชิกที่ต้องการ ไม่มีลักษณะการทำงานแบบรันไทม์และเลิกใช้ใน C ++ 17

โปรดทราบว่าแม้สิ่งนี้จะไม่สามารถใช้เป็นฐานร่วมกันได้เนื่องจากเทมเพลตของชั้นเรียนไม่ได้เป็นคลาส



5

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

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