C ++ Iterator, Mengapa tidak ada kelas dasar Iterator semua iterator mewarisi dari


11

Saya belajar untuk ujian dan saya punya pertanyaan yang saya berjuang untuk memberi dan menjawab.

Mengapa tidak ada kelas dasar iterator semua iterator lain mewarisi dari?

Dugaan saya, guru saya merujuk pada struktur hierarkis dari referensi cpp " http://prntscr.com/mgj542 " dan kami harus memberikan alasan lain daripada mengapa mereka harus melakukannya?

Saya tahu apa itu iterator (semacam) dan bahwa mereka digunakan untuk bekerja di wadah. Dari apa yang saya mengerti karena kemungkinan struktur datastruktur yang berbeda, kontainer yang berbeda memiliki iterator yang berbeda karena Anda dapat secara acak mengakses array, misalnya, tetapi bukan daftar yang ditautkan dan kontainer yang berbeda memerlukan cara yang berbeda untuk bergerak melaluinya.

Mereka mungkin template khusus tergantung pada wadah, kan?


2
Berbagi penelitian Anda membantu semua orang . Beri tahu kami apa yang telah Anda coba dan mengapa itu tidak memenuhi kebutuhan Anda. Ini menunjukkan bahwa Anda telah meluangkan waktu untuk mencoba membantu diri sendiri, itu menyelamatkan kami dari mengulangi jawaban yang jelas, dan yang paling penting itu membantu Anda mendapatkan jawaban yang lebih spesifik dan relevan. Lihat juga Cara Meminta
nyamuk

5
" Mengapa tidak ada kelas dasar iterator semua iterator lain mewarisi dari? " Um ... mengapa harus ada satu?
Nicol Bolas

Jawaban:


14

Anda sudah mendapatkan jawaban yang menunjukkan mengapa semua iterator tidak perlu mewarisi dari kelas dasar Iterator tunggal. Aku sudah agak jauh lebih jauh. Salah satu tujuan C ++ adalah abstraksi dengan nol biaya run-time.

Jika iterator bekerja dengan mereka semua mewarisi dari kelas dasar umum, dan menggunakan fungsi virtual di kelas dasar untuk mendefinisikan antarmuka, dan kelas turunan menyediakan implementasi dari fungsi-fungsi virtual, yang bisa (dan sering akan) menambah run-time substansial overhead ke operasi yang terlibat.

Mari kita pertimbangkan, misalnya, hierarki iterator sederhana yang memang menggunakan fungsi pewarisan dan virtual:

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) {}
};

Kalau begitu mari kita lakukan tes cepat:

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";
}

[note: tergantung pada kompiler Anda, Anda mungkin perlu melakukan sedikit lagi, seperti mendefinisikan iterator_category, difference_type, referensi, dan sebagainya, agar kompiler menerima iterator.]

Dan hasilnya adalah:

y
y
time1: 1833ns
time2: 2933ns

[Tentu saja, jika Anda menjalankan kode, hasil Anda tidak akan sama persis dengan ini.]

Jadi, bahkan untuk kasus sederhana ini (dan hanya melakukan sekitar 80 peningkatan dan perbandingan) kami telah menambahkan sekitar 60% overhead ke pencarian linear sederhana. Terutama ketika iterator awalnya ditambahkan ke C ++, beberapa orang tidak akan menerima desain dengan overhead yang banyak. Mereka mungkin tidak akan distandarisasi, dan bahkan jika ada, hampir tidak ada yang akan menggunakannya.


7

Perbedaannya adalah antara Apa itu sesuatu, dan Bagaimana sesuatu berperilaku.

Banyak bahasa mencoba menyatukan keduanya, tetapi mereka adalah hal yang sangat berbeda.

Jika Bagaimana Apa, dan Apa Bagaimana ...

Jika semuanya mewarisi objectmaka beberapa manfaat terjadi seperti: variabel objek apa pun dapat memiliki nilai apa pun. Tapi itu juga intinya, semuanya harus berperilaku ( bagaimana ) seperti object, dan terlihat seperti ( apa ) object.

Tapi:

  • Bagaimana jika objek Anda tidak memiliki definisi kesetaraan yang bermakna?
  • Bagaimana jika itu tidak memiliki hash yang bermakna?
  • Bagaimana jika objek Anda tidak dapat dikloning, tetapi objek bisa?

Entah objecttipe menjadi tidak berguna - karena objek tidak memberikan kesamaan di semua contoh yang mungkin. Atau ada akan ada benda-benda yang telah patah / sepatu bertanduk / definisi absurd beberapa properti yang universal diduga ditemukan di objectmana provies hampir perilaku yang universal kecuali untuk sejumlah gotchas.

Jika Apa yang tidak terikat dengan Bagaimana

Bergantian Anda dapat menyimpan Apa dan Bagaimana . Kemudian beberapa Jenis berbeda (dengan tidak ada kesamaan sama sekali apa ) semua bisa berperilaku dengan cara yang sama seperti yang dilihat dari kolaborator bagaimana . Dalam pengertian ini gagasan tentang Iteratorbukanlah apa yang spesifik , tetapi bagaimana . Khususnya Bagaimana Anda berinteraksi dengan sesuatu ketika Anda belum tahu dengan apa Anda berinteraksi.

Java (dan sejenisnya) memungkinkan pendekatan untuk ini dengan menggunakan antarmuka. Antarmuka dalam hal ini menggambarkan sarana komunikasi, dan secara implisit protokol komunikasi dan tindakan yang diikuti. Apa Apa pun yang menyatakan dirinya sebagai Bagaimana , menyatakan bahwa itu mendukung komunikasi yang relevan dan tindakan yang digariskan oleh protokol. Ini memungkinkan setiap kolaborator untuk bergantung pada Bagaimana dan tidak macet dengan menentukan dengan tepat apa yang dapat digunakan.

C ++ (dan sejenisnya) memungkinkan pendekatan untuk ini dengan mengetik bebek. Templat tidak peduli jika tipe yang berkolaborasi menyatakan bahwa ia mengikuti perilaku, hanya saja dalam konteks kompilasi yang diberikan, bahwa objek dapat berinteraksi dengan cara tertentu. Ini memungkinkan C ++ pointer, dan Object over-riding operator tertentu untuk digunakan oleh kode yang sama. Karena mereka memenuhi daftar periksa untuk dianggap setara.

  • mendukung * a, a->, ++ a, dan ++ -> input / penerus iterator
  • mendukung * a, a->, ++ a, a ++, --a, dan a-- -> bidirectional iterator

Jenis yang mendasarinya bahkan tidak harus iterasi wadah, bisa apa saja . Selain itu memungkinkan beberapa kolaborator untuk menjadi lebih generik, bayangkan fungsi hanya perlu a++, iterator dapat memuaskan itu, begitu juga sebuah pointer, sehingga dapat sebuah integer, sehingga dapat mengimplementasikan objek apa pun operator++.

Spesifikasi Di Bawah dan Di Atas

Masalah dengan kedua pendekatan di bawah dan di atas spesifikasi.

Menggunakan antarmuka membutuhkan objek untuk menyatakan mendukung perilaku yang diberikan, yang juga berarti bahwa pencipta harus menambahkannya sejak awal. Hal ini menyebabkan beberapa Apa yang tidak membuat potongan, karena mereka tidak menyatakannya. Itu juga berarti apa adanya memiliki leluhur yang sama, antarmuka yang mewakili Bagaimana . Lingkaran ini kembali ke masalah awal object. Hal ini menyebabkan kolaborator terlalu menentukan persyaratan mereka, sementara secara bersamaan menyebabkan beberapa objek menjadi tidak dapat digunakan karena kurangnya deklarasi, atau disembunyikan gotcha karena perilaku yang diharapkan didefinisikan dengan buruk.

Menggunakan templat mengharuskan kolaborator bekerja dengan Apa yang sama sekali tidak dikenal , dan melalui interaksinya ia menentukan Bagaimana . Untuk beberapa hal ini membuat kolaborator menulis lebih sulit, karena ia harus menganalisis Apa untuk primitif komunikasinya (fungsi / bidang / dll) sambil menghindari kesalahan kompilasi, atau setidaknya menunjukkan bagaimana yang diberikan Apa yang tidak cocok dengan persyaratannya untuk Bagaimana . Ini memungkinkan kolaborator untuk meminta minimum absolut dari Apa yang diberikan , memungkinkan rentang terluas apa yang akan digunakan. Sayangnya ini memiliki kelemahan dari memungkinkan penggunaan benda yang tidak masuk akal yang teknis menyediakan komunikasi primitif untuk diberikanCaranya , tapi jangan ikuti protokol tersirat yang memungkinkan segala macam hal buruk terjadi.

Iterator

Dalam hal Iteratorini, a adalah Bagaimana itu adalah singkatan untuk deskripsi interaksi. Apa pun yang cocok dengan deskripsi itu adalah menurut definisi Iterator. Mengetahui Bagaimana memungkinkan kita untuk menulis algoritma umum dan memiliki daftar singkat ' Bagaimana diberi spesifik Apa ' yang perlu disediakan untuk membuat algoritma bekerja. Daftar itu adalah fungsi / properti / dll, implementasinya memperhitungkan spesifik Apa yang sedang ditangani oleh algoritma.


6

Karena C ++ tidak perlu memiliki kelas dasar (abstrak) untuk melakukan polimorfisme. Ini memiliki subtyping struktural serta subtipe nominatif .

Yang membingungkan dalam kasus Iterator tertentu, standar sebelumnya didefinisikan std::iteratorsebagai (kurang-lebih)

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;
}

Yaitu hanya sebagai penyedia jenis anggota yang diperlukan. Itu tidak memiliki perilaku runtime, dan tidak digunakan lagi di C ++ 17

Perhatikan bahwa meskipun ini tidak bisa menjadi basis umum, karena templat kelas bukan kelas, setiap instantiasi berdiri sendiri dari yang lain.



5

Salah satu alasannya adalah iterator tidak harus menjadi instance kelas. Pointer adalah iterator yang sangat baik dalam banyak kasus, misalnya, dan karena itu primitif, mereka tidak dapat diwarisi dari apa pun.

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.