web-dev-qa-db-ja.com

C ++ STLがテンプレートに大きく依存しているのはなぜですか? (*インターフェース*ではない)

つまり、義務的な名前(標準テンプレートライブラリ)は別として...

C++は当初、OOP概念をCに提示することを意図していました。つまり、特定のエンティティが(どのように行うかに関係なく)そのクラスとクラス階層に基づいて、できることとできないことを伝えることができます)いくつかの能力の構成は、多重継承の問題と、C++が(Javaなどと比較して)やや不器用な方法でインターフェースの概念をサポートするという事実により、この方法で説明するのがより困難です。改善される)。

そして、STLとともにテンプレートが登場しました。 STLは古典的なOOPの概念を取り入れ、代わりにテンプレートを使用してそれらをドレインに流し込むように見えました。

テンプレートを使用して型を一般化する場合は、型のテーマ自体がテンプレートの操作に関係ない場合(コンテナなど)に区別する必要があります。 vector<int>は完全に理にかなっています。

ただし、他の多くの場合(イテレータおよびアルゴリズム)、テンプレート化された型は、概念の実際の詳細がテンプレートの実装によって完全に定義される「コンセプト」(入力イテレータ、フォワードイテレータなど)に従うことになっていますテンプレートで使用される型のクラスではなく、OOPのやや反使用である関数/クラス。

たとえば、関数に伝えることができます:

void MyFunc(ForwardIterator<...> *I);

更新:元の質問では不明だったので、ForwardIteratorは、ForwardIteratorタイプを許可するようにテンプレート化しても構いません。それとは反対に、ForwardIteratorを概念として持っています。

以下の実装またはドキュメントを確認する必要がある定義を確認することによってのみ、フォワードイテレータが必要です。

template <typename Type> void MyFunc(Type *I);

テンプレートの使用を支持できる2つの主張:コンパイル済みのコードは、vtablesを使用する代わりに、使用するタイプごとにテンプレートをカスタマイズすることで、より効率的にすることができます。また、テンプレートをネイティブタイプで使用できるという事実。

しかし、古典的なOOP=を放棄してSTLのテンプレートを支持するより深い理由を探していますか?(ここまで読んだと仮定すると:P)

208
OB OB

簡単な答えは「C++が進んだため」です。はい、70年代後半に、StroustrupはOOP機能を備えたアップグレードされたCを作成するつもりでしたが、それはかなり前です。1998年に言語が標準化された頃には、 an OOP言語。これはマルチパラダイム言語でした。確かにOOPコードをサポートしていましたが、チューリング完全なテンプレート言語がオーバーレイされていました、それはコンパイル時のメタプログラミングを可能にし、人々は一般的なプログラミングを発見しました。突然、OOPはそれほど重要ではないように思われました。よりシンプルで簡潔なandテンプレートと一般的なプログラミングを通じて利用可能な技術を使用することにより、より効率的なコード。

OOPは聖杯ではありません。それはかわいいアイデアであり、それが発明された70年代の手続き型言語に比べてかなり改善されました。しかし、それは正直言って、それがすべてに割れているわけではありません。多くの場合、それは不器用で冗長であり、再利用可能なコードやモジュール性を実際には促進しません。

だからこそ、C++コミュニティは今日、ジェネリックプログラミングにはるかに興味を持ち、everyoneがついに関数型プログラミングも非常に賢いことに気付き始めています。 OOPそれ自体は見た目がよくありません。

架空の「OOP化」STLの依存関係グラフを作成してみてください。互いに知り合う必要のあるクラスはいくつですか?依存関係のlotがあります。 vectorまたはiteratorを取り込むことなく、iostreamヘッダーだけを含めることができますか? STLはこれを簡単にします。ベクトルは、それが定義するイテレータタイプを知っています。それがすべてです。 STLアルゴリズムはnothingを知っています。それらはすべてイテレータをパラメータとして受け入れますが、イテレータヘッダーを含める必要さえありません。どちらがよりモジュール化されていますか?

STLは、OOP as Javaで定義されているが、goalsOOP?再利用性、低結合、モジュール性、カプセル化を達成しませんか?

そして、これらの目標betterを達成しませんか?

STLが言語に採用された理由については、STLにつながったいくつかのことが起こりました。

まず、テンプレートがC++に追加されました。これらは、ジェネリックが.NETに追加されたのとほぼ同じ理由で追加されました。型の安全性を捨てずに「型Tのコンテナ」のようなものを書くことができるのは良い考えのようでした。もちろん、彼らが決めた実装は、はるかに複雑で強力でした。

その後、人々は、追加したテンプレートメカニズムが予想よりもさらに強力であることを発見しました。そして、誰かがテンプレートを使用してより一般的なライブラリを作成する実験を始めました。 1つは関数型プログラミングに触発され、もう1つはC++のすべての新しい機能を使用しました。

彼はそれをC++言語委員会に提示しましたが、C++言語委員会は慣れないものになるのにかなり時間がかかりましたが、見た目が変で違っていましたが、最終的には従来のOOPそれ以外の場合は同等のものを含める必要があります。したがって、彼らはそれをいくつか調整し、標準ライブラリに採用しました。

それはイデオロギー的な選択ではなく、「OOPかどうか」になりたい」という政治的な選択ではなく、非常に実用的な選択でした。彼らは図書館を評価し、それを見ましたとてもうまくいきました。

いずれにせよ、STLを支持する理由として挙げた理由はどちらも絶対に不可欠です。

C++標準ライブラリhasは効率的です。たとえば、同等の手巻きCコードよりも効率が悪い場合、人々はそれを使用しません。それは生産性を低下させ、バグの可能性を高め、全体としては悪い考えです。

また、STL hasはプリミティブ型で動作します。これは、プリミティブ型がCにあるすべてであり、両方の言語の主要な部分であるためです。 STLがネイティブ配列で機能しなかった場合、役に立たないになります。

あなたの質問には、OOPは「最高」です。理由を聞きたいと思います。なぜ「古典的なOOPを放棄したのか」と尋ねます。どの利点があったでしょうか?

590
jalf

私があなたが尋ねている/文句を言っていると思うことに対する最も直接的な答えはこれです:C++がOOP言語であるという仮定は間違った仮定です。

C++はマルチパラダイム言語です。 OOP原則を使用してプログラミングできます。手続き的にプログラミングできます。一般的に(テンプレート)プログラミングでき、C++ 11(以前はC++ 0xとして知られていました)でもプログラミングできます。機能的に。

C++の設計者はこれを利点とみなしているため、ジェネリックプログラミングが問題をよりよく、そしてより多くジェネリックに解決する場合、C++を純粋にOOP言語のように動作するように制約すると主張します、後退します。

86
Tyler McHenry

私の理解では、Stroustrupはもともと「OOPスタイルの」コンテナ設計を好んでおり、実際には他の方法はありませんでした。アレクサンダーステパノフはSTLを担当し、 彼の目標には「オブジェクト指向にする」が含まれていませんでした

それが基本的なポイントです。アルゴリズムは代数構造で定義されます。複雑さの要件を通常の公理に追加することで構造の概念を拡張する必要があることに気付くのに、さらに数年かかりました。 ...私は、イテレータ理論がコンピューターサイエンスの中心であり、リングまたはバナッハ空間の理論が数学の中心であると信じています。アルゴリズムを見るたびに、それが定義されている構造を見つけようとします。したがって、私がやりたかったのは、アルゴリズムを一般的に記述することでした。それが私がしたいことです。一般的な表現を見つけようとする有名なアルゴリズムに1か月を費やすことができます。 ...

少なくとも私にとって、STLはプログラミングが可能な唯一の方法です。確かに、C++プログラミングとはかなり異なっており、ほとんどの教科書で提示され、現在も提示されています。しかし、ご存知のように、私はC++でプログラミングしようとしていませんでした。ソフトウェアを扱う正しい方法を見つけようとしていました。 ...

多くの間違ったスタートがありました。たとえば、なぜそのメカニズムが根本的に欠陥があり、使用すべきではないのかを理解する前に、継承と仮想の用途を見つけるために何年も費やしました。誰も中間段階のすべてを見ることができなかったことにとても満足しています-それらのほとんどは非常に愚かでした。

(彼は、なぜ継承と仮想-​​別名オブジェクト指向設計が「根本的に欠陥があり、使用すべきではない」かについて、インタビューの残りの部分で説明しています)。

Stepanovが自分のライブラリをStroustrupに提示すると、Stroustrupと他の人たちはISO C++標準に到達するために多大な努力をしました(同じインタビュー)。

Bjarne Stroustrupのサポートは非​​常に重要でした。 Bjarneは本当に標準のSTLを望んでいて、Bjarneが何かを望んでいるなら、それを手に入れます。 ...彼は私にSTLの変更を強制しましたが、私は他の誰にも決してそれをしないでしょう...彼は私が知っている最も心の広い人です。彼は物事を成し遂げます。 STLが何であるかを理解するにはしばらく時間がかかりましたが、そのとき、彼はそれをプッシュする準備ができていました。彼はまた、STLに貢献して、複数のプログラミング方法が有効であるという見解に立ち向かいました。10年以上もの間、誇大広告や誇大広告はなく、柔軟性、効率、オーバーロード、およびタイプセーフの組み合わせを追求しました。 STLを可能にしたテンプレート。 Bjarneは私の世代の卓越した言語デザイナーであることをはっきりと述べたいと思います。

72
Max Lybbert

答えはこれにあります インタビュー Stepanov、STLの著者:

はい。 STLはオブジェクト指向ではありません。オブジェクト指向性は、人工知能とほぼ同じであると思います。これらのOO people。から来る興味深いコードをまだ見ていません。

23
StackedCrooked

なぜ純粋なOOP Data Structure&Algorithms Libraryの設計の方が良いでしょうか?!OOPはすべての解決策ではありません。

私見、STLは私が今まで見た中で最もエレガントなライブラリです:)

あなたの質問のために、

ランタイムポリモーフィズムは必要ありません。実際には、静的ポリモーフィズムを使用してライブラリを実装することはSTLの利点であり、効率を意味します。一般的なソートまたは距離、またはすべてのコンテナに適用されるアルゴリズムを記述してみてください! Sort in Javaは、実行されるnレベルを通じて動的な関数を呼び出します!

いわゆるピュアOOP言語の厄介な仮定を隠すために、ボクシングやアンボクシングのような愚かなものが必要です。

私がSTLで見た唯一の問題、および一般的なテンプレートは、ひどいエラーメッセージです。これは、C++ 0Xのコンセプトを使用して解決されます。

JavaでのSTLとコレクションの比較は、タージ・マハルと私の家を比較するようなものです:)

18
AraK

テンプレート化された型は、概念の実際の詳細が型のクラスではなくテンプレート関数/クラスの実装によって完全に定義される「概念」(入力イテレータ、フォワードイテレータなど)に従うことになっています。テンプレートで使用します。これは、OOPの多少の反使用です。

テンプレートによる概念の使用目的を誤解していると思います。たとえば、Forward Iteratorは非常に明確に定義された概念です。クラスがフォワードイテレータになるために有効でなければならない式、および計算の複雑さを含むそれらのセマンティクスを見つけるには、標準または http://www.sgi.com/tech/stl /ForwardIterator.html (すべてを表示するには、入力、出力、および簡易イテレータへのリンクをたどる必要があります)。

そのドキュメントは完全に優れたインターフェースであり、「コンセプトの実際の詳細」はそこで定義されています。これらは、Forward Iteratorの実装では定義されておらず、Forward Iteratorを使用するアルゴリズムでも定義されていません。

STLとJavaの間でインターフェースが処理される方法の違いは3つあります。

1)STLはオブジェクトを使用して有効な式を定義しますが、Javaはオブジェクトで呼び出し可能でなければならないメソッドを定義します。もちろん有効な式はメソッド(メンバー関数)呼び出しかもしれませんが、ある必要があります。

2)Javaインターフェイスはランタイムオブジェクトですが、RTLを使用しても実行時にSTLの概念は表示されません。

3)STLコンセプトに必要な有効な式の有効化に失敗した場合、テンプレートを型でインスタンス化すると、不特定のコンパイルエラーが発生します。 Javaインターフェースの必要なメソッドの実装に失敗すると、その旨の特定のコンパイルエラーが発生します。

この3番目の部分は、(コンパイル時の)一種の「ダックタイピング」が好きな場合です。インターフェイスは暗黙的です。 Javaでは、インターフェイスはいくぶん明示的です。クラスは、Iterableを実装するsays場合にのみIterableになります。コンパイラは、そのメソッドのシグネチャがすべて存在し、正しいことを確認できますが、セマンティクスはまだ暗黙的です(つまり、ドキュメント化されているかどうかはわかりませんが、実装が正しいかどうかを判断できるのはコード(ユニットテスト)だけです)。

C++では、Pythonと同様に、セマンティクスと構文の両方が暗黙的ですが、C++では(および強力な型付けプリプロセッサを取得している場合はPython)、コンパイラから何らかの助けが得られます。プログラマーは、実装クラスによるインターフェースのJavaのような明示的な宣言を必要とするので、標準的なアプローチは、型の特性を使用することです(そして、多重継承により、これが冗長になりすぎるのを防ぐことができます)。すべての必要な式が私のタイプに有効な場合にのみコンパイルされます。これにより、「使用する前に」必要なすべてのビットを実装したかどうかがわかります。それは便利ですが、そうではありませんOOPのコア(そして、まだセマンティクスをテストしていません。セマンティクスをテストするコードは、当然、問題の式の妥当性もテストします)。

STLは、あなたの好みに応じてOOかもしれませんが、インターフェースを実装から明確に分離します。インターフェースをリフレクションするJavaの能力が欠けており、インターフェース要件の違反を別々に報告します。

関数に伝えることができます...定義を見るだけでフォワードイテレータを期待します、そこでは実装またはドキュメントを見る必要があります...

個人的には、適切に使用する場合、暗黙の型は強みだと思います。アルゴリズムは、テンプレートパラメータを使用して何を行うかを指定し、実装者はそれらが機能することを確認します。これは、「インターフェイス」が行うべきことのまさに共通の分母です。さらに、STLを使用すると、たとえば、std::copyは、ヘッダーファイルで前方宣言を見つけることに基づいています。プログラマーは、関数の署名だけでなく、ドキュメントに基づいて関数が取るものを解決する必要があります。これは、C++、Python、またはJavaに当てはまります。任意の言語でのタイピングで達成できることには制限があり、タイピングを使用してそれが実行しないこと(セマンティクスのチェック)を試みるとエラーになります。

そうは言っても、STLアルゴリズムは通常、どのコンセプトが必要かを明確にする方法でテンプレートパラメータに名前を付けます。ただし、これはドキュメントの最初の行に有用な追加情報を提供するためであり、前方宣言をより有益なものにするためではありません。パラメータのタイプにカプセル化できる以上のことを知る必要があるので、ドキュメントを読む必要があります。 (たとえば、入力範囲と出力イテレータを使用するアルゴリズムでは、入力イテレータのサイズとその中の値に基づいて、出力イテレータが特定の数の出力に対して十分な「スペース」を必要とする可能性があります。 )

明示的に宣言されたインターフェイスに関するBjarneは次のとおりです。 http://www.artima.com/cppsource/cpp0xP.html

ジェネリックでは、引数はジェネリックの定義で指定されたインターフェイス(インターフェイスに相当するC++は抽象クラス)から派生したクラスである必要があります。つまり、すべての汎用引数タイプは階層に収まらなければなりません。それは設計に不必要な制約を課すため、開発者側の不合理な先見が必要です。たとえば、ジェネリックを書いてクラスを定義した場合、指定したインターフェイスを知っていて、そこからクラスを派生していない限り、人々はジェネリックの引数として私のクラスを使用できません。それは厳格です。

逆に見てみると、アヒルのタイピングを使用すると、インターフェイスが存在することを知らずにインターフェイスを実装できます。または、クラスが実装するように意図的にインターフェイスを記述し、ドキュメントを参照して、まだ行っていないことを要求しないことを確認できます。それは柔軟です。

11
Steve Jessop

「OOPとは、メッセージング、ローカルの保持と状態プロセスの保護と隠蔽、およびすべての事柄の極端な遅延バインディングのみを意味します。SmalltalkとLISPで実行できます。これが可能なシステムは他にもありますが、私はそれらを知りません。」 -Smalltalkの作成者であるAlan Kay。

C++、Java、および他のほとんどの言語はすべて、従来のOOPとはかなり異なります。とはいえ、イデオロギーを主張することはそれほど生産的ではありません。 C++はいかなる意味でも純粋ではないため、その時点では実用的な意味をなす機能を実装しています。

8
Ben Hughes

STLは、最も一般的に使用されるアルゴリズムをカバーする大規模なライブラリを提供することを意図して開始しました-コンサイトの動作とperformanceをターゲットにしています。テンプレートは、その実装とターゲットを実現可能にするための重要な要素として来ました。

別の参照を提供するだけです:

アルスティーブンスは1995年3月にDDJのアレックスステパノフにインタビューします。

ステパノフは、彼の仕事の経験と、最終的にSTLに進化したアルゴリズムの大きなライブラリに対する選択を説明しました。

ジェネリックプログラミングへの長期的な関心について教えてください

.....その後、C++ライブラリのC++グループで働くBell Laboratoriesで仕事を提供されました。彼らは私にC++でそれができるかどうか尋ねました。もちろん、私はC++を知らなかったし、もちろん、できると言った。しかし、1987年にはC++にはこのスタイルのプログラミングを可能にするために不可欠なテンプレートがなかったため、C++ではできませんでした。継承は汎用性を得るための唯一のメカニズムであり、十分ではありませんでした。

現在でも、C++の継承は汎用プログラミングにはあまり役立ちません。理由を説明しましょう。多くの人々は、継承を使用してデータ構造とコンテナクラスを実装しようとしました。現在わかっているように、成功した試みはほとんどありませんでした。 C++の継承、およびそれに関連するプログラミングスタイルは劇的に制限されています。それを使用して平等と同じくらい些細なことを含む設計を実装することは不可能です。階層のルートにある基本クラスXで開始し、タイプXの引数を取るこのクラスで仮想等値演算子を定義する場合、クラスXからクラスYを導出します。等式のインターフェースは何ですか? YとXを比較する平等性があります。動物を例にとると(OO人は動物が大好きです)、哺乳類を定義し、哺乳類からキリンを導き出します。次に、動物が動物と交配して動物を返すメンバー関数合致を定義します。次に、動物からキリンを派生させます。もちろん、キリンは動物と交尾し、動物を返す関数メイトを持っています。それは間違いなくあなたが望むものではありません。交配はC++プログラマにとってそれほど重要ではないかもしれませんが、平等は重要です。ある種の平等が使用されていない単一のアルゴリズムは知りません。

6
yowkee

の基本的な問題

void MyFunc(ForwardIterator *I);

イテレータが返すものの型を安全に取得するにはどうすればよいですか?テンプレートを使用すると、コンパイル時にこれが行われます。

5
anon

しばらくの間、標準ライブラリを基本的にコレクションとアルゴリズムのデータベースと考えてみましょう。

データベースの歴史を研究したことがある人なら、最初からデータベースがほとんど「階層的」であることをご存じでしょう。階層データベースは、従来のOOPに非常に密接に対応していました。具体的には、Smalltalkで使用されているような単一継承の品種です。

時間が経つにつれて、階層型データベースを使用してほとんどすべてのモデルを作成できることが明らかになりました。but場合によっては、単一継承モデルはかなり制限されていました。木製のドアがある場合は、ドアとして、またはいくつかの原料(鋼鉄、木材など)として見ることができると便利でした。

そこで、彼らはネットワークモデルデータベースを発明しました。ネットワークモデルデータベースは、多重継承に非常に密接に対応しています。 C++は多重継承を完全にサポートしますが、Javaは制限された形式をサポートします(1つのクラスからのみ継承できますが、必要な数のインターフェイスを実装することもできます)。

階層モデルとネットワークモデルの両方のデータベースは、一般的な用途からほとんど消えています(ただし、かなり特定のニッチに残っているものもあります)。ほとんどの場合、それらはリレーショナルデータベースに置き換えられています。

リレーショナルデータベースが引き継いだ理由の多くは汎用性でした。リレーショナルモデルは、機能的にはネットワークモデルのスーパーセットです(つまり、階層モデルのスーパーセットです)。

C++はほぼ同じ道をたどっています。単一継承と階層モデルの間、および多重継承とネットワークモデルの間の対応はかなり明白です。 C++テンプレートと階層モデルの間の対応はそれほど明白ではないかもしれませんが、とにかくかなりぴったりです。

正式な証拠はまだ見ていませんが、テンプレートの機能は、多重継承によって提供される機能のスーパーセットであると考えています(明らかに、単一継承のスーパーセットです)。 1つ注意すべき点は、テンプレートはほとんど静的にバインドされていることです。つまり、すべてのバインドは実行時ではなくコンパイル時に行われます。そのため、継承が継承機能のスーパーセットを提供するという正式な証明は、やや困難で複雑な場合があります(または不可能な場合もあります)。

いずれにせよ、それはC++がコンテナに継承を使用しない本当の理由のほとんどだと思います。継承はテンプレートによって提供される機能のサブセットのみを提供するため、そうする本当の理由はありません。テンプレートは基本的に必要な場合があるため、ほぼどこでも使用できます。

2
Jerry Coffin

ForwardIterator *との比較はどのように行いますか?つまり、あなたが持っているアイテムが探しているものであるか、それを通過したかをどのように確認しますか?

ほとんどの場合、次のようなものを使用します。

void MyFunc(ForwardIterator<MyType>& i)

つまり、私はMyTypeを指していることを知っており、それらを比較する方法を知っています。テンプレートのように見えますが、実際にはそうではありません(「テンプレート」キーワードはありません)。

0
Tanktalus