私は試験に向けて学習しており、質問と回答に苦労しています。
なぜ他のすべてのイテレータが継承するイテレータ基本クラスが存在しないのですか?
私の先生はcppリファレンス " http://prntscr.com/mgj542 "の階層構造を参照していると思いますが、なぜそれ以外の理由を提供する必要があるのでしょうか。
イテレータとは何か(一種)であり、イテレータがコンテナでの作業に使用されていることを知っています。考えられる根本的なデータ構造が異なるため、たとえば、配列にはランダムにアクセスできますが、リンクされたリストにはアクセスできないため、コンテナーごとに異なるイテレーターがあり、コンテナーごとに異なる移動方法が必要です。
それらはおそらくコンテナに応じて特殊化されたテンプレートでしょう。
すべてのイテレーターが単一のイテレーター基本クラスから継承する必要がない理由を指摘する回答はすでに得ています。でもかなり遠いです。 C++の目標の1つは、実行時コストがゼロの抽象化です。
共通の基本クラスから継承するすべてのイテレータが機能し、基本クラスの仮想関数を使用してインターフェイスを定義し、派生クラスがそれらの仮想関数の実装を提供した場合、実質的なランタイムが追加される可能性があります(多くの場合)含まれる操作へのオーバーヘッド。
たとえば、継承と仮想関数を使用する単純なイテレータ階層について考えてみましょう。
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、difference_type、参照などの定義など、もう少し作業が必要になる場合があります。]
そして出力は:
y
y
time1: 1833ns
time2: 2933ns
[もちろん、コードを実行すると、結果はこれらと完全には一致しません。]
したがって、この単純なケース(および約80の増分と比較のみ)の場合でも、単純な線形検索に約60%のオーバーヘッドを追加しました。特にイテレータが最初にC++に追加されたとき、かなりのオーバーヘッドを持つデザインを受け入れなかった人がかなりいます。それらはおそらく標準化されていなかったでしょうし、たとえ標準化されていたとしても、実質的に誰もそれらを使用しませんでした。
違いはWhat何かとHow何かが動作することです。
多くの言語が2つを融合させようとしますが、それらはまったく異なるものです。
How is what、what What is How ...
すべてがobject
から継承される場合、次のようないくつかの利点が発生します。オブジェクトの任意の変数は、任意の値を保持できます。しかし、それは問題でもあり、すべてmustはobject
のように(the how)のように動作し、(the whatのように見えます)object
。
だが:
object
型は、オブジェクトがすべての可能なインスタンスに共通性を提供しないため、本質的に役に立たなくなります。または、object
にあるいくつかの推定ユニバーサルプロパティの壊れた/靴の角/不合理な定義を持つオブジェクトが存在し、いくつかの問題を除いてalmostユニバーサル動作を提供します。
何がどのように拘束されていない場合
または、WhatとHowを別々にしておくこともできます。次に、いくつかの異なるタイプ(共通点は何もないthe what)はすべて、コラボレーターから見た場合と同じように動作できますthe how。この意味で、Iterator
の概念は特定のwhatではなく、howです。具体的には方法まだ知らないときに何かとやりとりします何やり取りしています。
Java(および同様のもの)は、インターフェースを使用することによってこれへのアプローチを可能にします。この点でのインターフェースは、通信の手段、および暗黙的に通信のプロトコルと実行されるアクションを記述します。任意のWhatは、それ自体が特定のHowであることを宣言し、プロトコルによって概説される関連する通信およびアクションをサポートすることを示します。これにより、どの共同編集者もHowに依存することができ、使用できるWhatを正確に指定することで手間がかかりません。
C++(および同様の)では、ダックタイピングによってこれにアプローチできます。テンプレートは、特定のコンパイルコンテキスト内で、特定の方法でオブジェクトと対話できるという動作に従うことを共同作業型が宣言しているかどうかを考慮しません。これにより、C++ポインター、および特定の演算子をオーバーライドするオブジェクトを同じコードで使用できます。同等と見なされるためのチェックリストを満たしているため。
基本となる型は、コンテナを反復する必要はなく、任意のwhatにすることができます。さらに、一部のコラボレーターをさらに汎用的にすることができます。関数に必要なのはa++
、イテレータはそれを満たすことができ、ポインタも整数もできるので、operator++
。
仕様の下および上
両方のアプローチの問題は仕様の過不足です。
インターフェースを使用するには、オブジェクトが特定の動作をサポートすることを宣言する必要があります。これは、作成者が最初からそれを組み込む必要があることも意味します。これにより、宣言されなかったWhatがカットを行わなくなります。また、これはWhatに共通の祖先、つまりHowを表すインターフェイスがあることを意味します。これはobject
の最初の問題に戻ります。これにより、共同作業者は要件を過剰に指定すると同時に、一部のオブジェクトが宣言の欠如のために使用できなくなったり、期待される動作が十分に定義されていないために隠されたりします。
テンプレートを使用するには、コラボレーターが完全に不明なWhatと連携する必要があり、その相互作用を通じてHowを定義します。コンパイルエラーを回避しながら通信プリミティブ(関数/フィールド/その他)のWhatを分析するか、少なくとも特定の何方法の要件に一致しません。これにより、コラボレーターは特定のWhatからの絶対最小値を要求し、最も広い範囲のwhatを使用できるようになります。残念ながら、これには、特定のHowの通信プリミティブを技術的に提供するオブジェクトの無意味な使用を許可するという短所がありますが、あらゆる種類の悪いことを許可する暗黙のプロトコルには従いません。
イテレータ
この場合、Iterator
はHowです。相互作用の説明の省略形です。その説明に一致するものはすべて、定義上、Iterator
です。 Howを知ることで、一般的なアルゴリズムを記述し、提供する必要がある特定のWhatを指定した 'Howの短いリストを作成できます。アルゴリズムを機能させるための順序。そのリストはfunction/properties/etcであり、それらの実装は、アルゴリズムによって処理されている特定のWhatを考慮に入れます。
C++は、ポリモーフィズムを実行するための基本クラスを(抽象化して)持つ必要がない必要がないためです。 構造サブタイプ と 主なサブタイプ があります。
紛らわしいことに、イテレータの特定のケースでは、以前の標準が定義されました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では非推奨でした
クラステンプレートはクラスではないため、これが共通のベースになることはできません。各インスタンス化は他のインスタンス化から独立しています。
1つの理由は、イテレータがクラスのインスタンスである必要がないことです。たとえば、多くの場合、ポインターは完全に優れたイテレーターであり、それらはプリミティブであるため、何からも継承することはできません。