C ++ STLが非常にテンプレートに基づいているのはなぜですか?(*インターフェイス*ではありません)


211

つまり、必須の名前(標準テンプレートライブラリ)は別として...

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

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

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

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

たとえば、関数に次のように伝えることができます。

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

更新:元の質問では不明確だったので、ForwardIteratorをテンプレートにしてすべてのForwardIteratorタイプを許可しても問題ありません。逆に、ForwardIteratorをコンセプトとして使用しています。

は、その定義を見るだけでForward Iteratorを期待します。ここで、実装またはドキュメントのいずれかを調べる必要があります。

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

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

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


4
あなたはstackoverflow.com/questions/31693/…をチェックアウトするかもしれません。受け入れられた回答は、ジェネリックよりもテンプレートが提供するものの優れた説明です。
James McMahon、

6
@ジョナス:それは意味がありません。キャッシュの制約にはクロックサイクルがかかるため、それが重要です。結局のところ、パフォーマンスを定義するのは、キャッシュではなくクロックサイクルです。メモリとキャッシュは、費やされるクロックサイクルに影響を与える限りにおいてのみ重要です。また、実験も簡単に行えます。たとえば、同等のOOP / vtableアプローチを使用して、ファンクタ引数で呼び出されたstd :: for_Eachを比較してください。パフォーマンスの違いは驚異的です。これが、テンプレートバージョンが使用される理由です。
ジャルフ2009年

7
冗長なコードがicacheをいっぱいにする理由はありません。プログラムでvector <char>とvector <int>をインスタンス化する場合、vector <int>の処理中にvector <char>コードをicacheにロードする必要があるのはなぜですか?実際、casting、vtables、indirectionのコードを含める必要がないため、vector <int>のコードはトリミングされています。
jalf

3
アレックスステパノフ、継承と平等がうまく連携しない理由を説明しています。
fredoverflow

6
@BerndJendrissek:うーん、近いですが、あなた自身ではありません。はい、実際に使用する場合、メモリ帯域幅とキャッシュ使用量の観点からコードコストが増加します。しかし、期待していなければならない特別な理由がないvector<int>vector<char>同時に使用することは。彼らは確かに、かもしれませんが、あなたが使用する可能性のあるいかなる同時にコードを2枚。これは、テンプレート、C ++、またはSTLとは関係ありません。のインスタンス化にvector<int>は、vector<char>コードのロードまたは実行が必要なものはありません。
2013年

回答:


607

短い答えは「C ++が進んだため」です。はい、70年代後半に、StroustrupはOOP機能を備えたアップグレードされたCを作成することを意図していましたが、それはずっと前のことです。言語が1998年に標準化されたときには、それはもはやOOP言語ではありませんでした。それはマルチパラダイム言語でした。確かにOOPコードをある程度サポートしていましたが、Turing-completeテンプレート言語がオーバーレイされており、コンパイル時のメタプログラミングが可能であり、一般的なプログラミングを発見していました。突然、OOPはそれほど重要ではなくなったようです。テンプレートや一般的なプログラミングで利用できる手法を使用して、よりシンプルで簡潔効率的なコードを記述できる場合はそうではありません。

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

これが、C ++コミュニティが今日、ジェネリックプログラミングにはるかに関心を寄せている理由です。単独でのOOPは、かなりの光景ではありません。

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

Javaで定義されているように、STLはOOPのルールに従っていない可能性がありますが、OOPの目標を達成していませんか?再利用性、低カップリング、モジュール性、カプセル化を実現していませんか?

そして、OOP化されたバージョンよりもこれらの目標を達成しませんか?

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

最初に、テンプレートがC ++に追加されました。ジェネリックが.NETに追加されたのと同じ理由で追加されました。タイプセーフティを破棄せずに「タイプTのコンテナ」のようなものを書くことができるのは良い考えのようです。もちろん、彼らが着手した実装は、はるかに複雑で強力でした。

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

彼はC ++言語委員会にそれを提示しました。委員会は、奇妙で違って見えるために慣れるまでにかなりの時間を要しましたが、最終的には他の方法で含める必要がある従来のOOP同等物よりもうまく機能することに気付きました。そこで彼らはいくつかの調整を行い、それを標準ライブラリに採用しました。

それはイデオロギー的な選択ではなく、「OOPになりたいかどうか」という政治的な選択ではなく、非常に実用的な選択でした。彼らはライブラリを評価し、それが非常にうまく機能していることを確認しました。

いずれにしても、STLを支持する理由としてあなたが言及した2つの理由はどちらも絶対に不可欠です。

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

そして、STL プリミティブ型を処理する必要があります。これは、プリミティブ型がCにあるすべてのものであり、それらは両方の言語の主要な部分だからです。STLがネイティブ配列で機能しない場合、それは役に立たないでしょう。

あなたの質問には、OOPが「最高」であるという強い前提があります。その理由を知りたいです。彼らはなぜ「古典的なOOPを放棄したのか」と尋ねます。なぜ彼らはそれにこだわるべきだったのだろう。それにはどのような利点がありますか?


22
それは良い記事ですが、1つの詳細を強調したいと思います。STLはC ++の「製品」ではありません。実際、STLは概念としてC ++の前に存在し、C ++はたまたま一般的なプログラミングに(ほぼ)十分な能力を備えた効率的な言語なので、STLはC ++で作成されました。
イゴールKrivokon

17
コメントで取り上げられ続けているので、はい、STLの名前があいまいであることを認識しています。しかし、「STLに基づいてモデル化されたC ++標準ライブラリの一部」というより良い名前は思いつきません。標準ライブラリの一部のための事実上の名前があり、それは厳密に不正確であっても、単に「STL」。:)人々が標準ライブラリ全体(IOStreamsとC stdlibヘッダーを含む)の名前としてSTLを使用しない限り、私は満足しています。:)
jalf

5
@einpoklumそして、抽象基底クラスから正確に何を得るのでしょうか?std::set例として考えてみましょう。抽象基本クラスから継承しません。それはあなたの使用をどのように制限しますstd::setか?std::set抽象基本クラスから継承しないため、でできないことはありますか?
fredoverflow 2014年

22
@einpoklumは、Alan KayがOOPという用語を発明したときにOOP言語になるように設計したSmalltalk言語を見てください。インターフェースはありませんでした。OOPは、インターフェースや抽象基本クラスに関するものではありません。あなたは「用語のOOPの発明者が考えていたもののようなものではありませんJavaは、C ++よりもOOPであることを言おうとしているにも長期的なOOPの発明者が考えていたもののようなものではありませんか」?あなたが言うことは「C ++は私の好みには十分Javaに似ていない」ということです。それは公平ですが、OOP とは関係ありません
jalf

8
@MasonWheelerこの答えは、あなたがそうやってわずか3人でこれに世界の投票+1周り文字通り開発者の何百ものを見ていないでしょう露骨なナンセンスの束だった場合
パンダ- 34

88

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

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

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


4
「C ++ 0xを使用すると、機能的にプログラムすることもできます」-これらの機能がなくても、機能的にプログラムできます。
JonasKölker、2009年

3
@Tyler確かに、C ++を純粋なOOPに制限した場合、Objective-Cのままになります。
Justicle '28 / 07/28

@TylerMcHenry:ちょうどこれを尋ねただけで、私はあなたと同じ答えを口にしたことがわかりました!たった一点。標準ライブラリを使用してオブジェクト指向コードを記述できないことを付け加えてください。
アインポクルム2013

74

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

それが基本的なポイントです。アルゴリズムは代数構造で定義されます。通常の公理に複雑さの要件を追加することで構造の概念を拡張する必要があることを理解するのに、もう2年かかりました。...環やバナッハ空間の理論が数学の中心であるのと同様に、反復理論はコンピュータサイエンスの中心であると思います。アルゴリズムを見るたびに、それが定義されている構造を見つけようとしました。だから私がやりたかったのは、アルゴリズムを一般的に説明することでした。それが私がやりたいことです。1か月かけて、その一般的な表現を見つけようとする有名なアルゴリズムに取り組んでいます。...

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

多くの誤ったスタートがありました。たとえば、継承と仮想の用途を見つけるために何年も費やしましたが、そのメカニズムに根本的な欠陥があり、使用すべきではない理由を理解していませんでした。すべての中間ステップを誰も見ることができなかったことをとても嬉しく思います。それらのほとんどは非常にばかげていました。

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

ステパノフが自分のライブラリをStroustrupに提示すると、Stroustrupや他の人たちはそれをISO C ++標準に組み込むための多大な努力を重ねました(同じインタビュー)。

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


2
興味深いインタビュー。かなり前に読んだことは確かですが、間違いなくもう一度読む価値はありました。:)
jalf

3
私が今まで読んだプログラミングに関する最も興味深いインタビューの1つ。詳細については
喉が渇き

彼がJavaのような言語について行う不満の多く(「あるタイプの2つの引数を取り、その同じタイプの戻り値を持つJavaで一般的なmax()を書くことはできません」)は、非常に初期のバージョンにのみ関連していたジェネリックが追加される前の言語の。最初からでも、ジェネリックが最終的に追加されることが知られていましたが(実行可能な構文/セマンティクスが判明すると)、彼の批判はほとんど根拠がありません。はい、ある型のジェネリックは、静的に型付けされた言語で型の安全性を維持するために必要ですが、それでもOOの価値がなくなりません。
いくつかのガイ

1
@SomeGuy彼らはJava自体についての不満ではありません。彼は「SmallTalkの「標準的な」オブジェクト指向プログラミング、つまりJava」について話している。インタビューは90年代後半からです(彼はSGIで働いていたと述べていますが、彼は2000年にAT&Tで働くために退職しました)。Genericsは2004年にバージョン1.5でJavaに追加されただけで、「標準」のOOモデルからの逸脱です。
メルポメン

24

その答えは、STLの作者であるステパノフとのこのインタビューで見つかります。

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


素敵な宝石。それは何年からですか?
Kos

2
@ Kos、web.archive.org / web / 20000607205939 / http://www.stlport.org/…によれば、リンクされたページの最初のバージョンは2001年6月7日のものです。下部のページ自体にはCopyright 2001-と書かれています2008。
alfC 2015

@Kos Stepanovは、最初の回答でSGIで働くことについて言及しています。彼は2000年5月にSGIを去ったので、おそらくインタビューはそれより古いです。
メルポメン

18

データ構造とアルゴリズムライブラリの純粋なOOP設計のほうが優れているのはなぜですか。OOPはすべての問題の解決策ではありません。

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

あなたの質問のために、

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

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

STLと私が目にする唯一の問題は、テンプレート一般はひどいエラーメッセージです。これは、C ++ 0Xの概念を使用して解決されます。

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


12
タージマハルは小さくてエレガントで、あなたの家は山の大きさで、完全に混乱していますか?;)
jalf

概念はもはやc ++ 0xの一部ではありません。エラーメッセージの一部は、static_assertおそらく使用してプリエンプトできます。
KitsuneYMG 2010

GCC 4.6ではテンプレートエラーメッセージが改善されており、4.7 +の方がさらに優れていると思います。
David Stone

コンセプトは本質的に、OPが求めていた「インターフェース」です。唯一の違いは、概念からの「継承」が明示的ではなく暗黙的(クラスにすべての適切なメンバー関数がある場合、それは自動的に概念のサブタイプです)です(Javaクラスはインターフェースを実装することを明示的に宣言する必要があります) 。ただし、暗黙のサブタイプと明示的なサブタイプの両方が有効なオブジェクト指向であり、一部のオブジェクト指向言語には、概念と同様に機能する暗黙の継承があります。つまり、ここで言っているのは、基本的に「OOはうんざりする:テンプレートを使用することです。しかし、テンプレートには問題があるので、コンセプト(OOです)を使用してください。
いくつかの男

11

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

テンプレートによるコンセプトの使用目的を誤解していると思います。たとえば、Forward Iteratorは、非常に明確に定義された概念です。クラスが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では、インターフェースはいくぶん明示的です。クラスは「である」場合に限り、反復可能です。実装しいると言った。コンパイラは、そのメソッドのシグネチャがすべて存在し、正しいことを確認できますが、セマンティクスは依然として暗黙的です(つまり、ドキュメント化されているかどうかは不明ですが、実装が正しいかどうかを確認できるのはより多くのコード(単体テスト)だけです)。

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

STLは好みに応じて十分なオブジェクト指向である場合とそうでない場合がありますが、実装からインターフェースを明確に分離します。これには、インターフェースを介してリフレクションを実行するJavaの機能がなく、インターフェース要件の違反を異なる方法で報告します。

あなたは関数に伝えることができます...その定義を見ることによってのみフォワードイテレータを期待します、あなたは実装またはドキュメントのどちらかを見なければならないでしょう...

個人的には、暗黙の型が適切に使用される場合の強みだと思います。アルゴリズムは、テンプレートパラメータを使用して何を行うかを示し、実装者はそれらが機能することを確認します。これは、「インターフェース」が何をすべきかについての一般的な特徴です。さらに、STLを使用すると、たとえば、std::copyヘッダーファイルでその前方宣言を見つけることに基づいて使用することはほとんどありません。プログラマー、関数のシグニチャーだけでなく、そのドキュメントに基づいて、関数が何をとるかを考えなければなりません。これは、C ++、Python、またはJavaに当てはまります。どの言語でタイピングしても何ができるかには制限があり、タイピングを使用してそれを行わないこと(セマンティクスをチェック)を試みるとエラーになります。

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

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

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

逆に見ると、ダックタイピングを使用すると、インターフェースが存在することを知らなくてもインターフェースを実装できます。または、誰かが意図的にインターフェイスを作成して、クラスがそれを実装するようにし、ドキュメントを参照して、まだ行っていないことを要求しないことを確認します。それは柔軟です。


明示的に宣言されたインターフェースでは、2つの単語:型クラス。(これはステパノフが「概念」で意味しているものです。)
ピヨン

「STLコンセプトに必要な有効な式を有効にできなかった場合、型を使用してテンプレートをインスタンス化すると、詳細不明のコンパイルエラーが発生します。」-それは誤りです。std概念に一致しないものをライブラリに渡すことは、通常「形式が正しくないため、診断は必要ありません」。
Yakk-Adam Nevraumont

確かに、私は「有効」という言葉を使って、速くてルーズにプレーしていた。コンパイラが必要な式の1つをコンパイルできない場合、それは何かを報告します。
スティーブジェソップ

8

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

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


7

STLは、一貫した動作とパフォーマンスを目標として、最も一般的に使用されるアルゴリズムをカバーする大規模なライブラリを提供することを目的として始まりました。テンプレートは、その実装とターゲットを実現可能にするための重要な要素でした。

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

Al StevensがDDJの1995年3月にAlex Stepanovにインタビュー:

ステパノフは、彼の仕事の経験とアルゴリズムの大規模なライブラリに向けて行われた選択を説明し、最終的にはSTLに進化しました。

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

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

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


5

の基本的な問題

void MyFunc(ForwardIterator *I);

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


1
まあ、私は次のいずれかです。1.汎用コードを記述しているので、取得しようとしないでください。または、2。C ++が最近提供しているリフレクションメカニズムを使用して取得します。
アインポクルム2013

2

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

データベースの歴史を研究したことがあれば、データベースのほとんどが「階層的」であったことは間違いありません。階層型データベースは、古典的なOOPに非常によく対応していました。具体的には、Smalltalkで使用されているような単一継承の種類です。

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

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

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

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

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

私はそれを正式に証明したことはありませんが、テンプレートの機能は、複数の継承によって提供される機能のスーパーセットであると確信しています(これは明らかに単一の継承のスーパーセットです)。トリッキーな部分の1つは、テンプレートがほとんど静的にバインドされることです。つまり、すべてのバインドは、実行時ではなくコンパイル時に行われます。このように、継承が継承の機能のスーパーセットを提供するという正式な証明は、かなり困難で複雑な場合があります(または不可能である場合もあります)。

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


0

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

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

void MyFunc(ForwardIterator<MyType>& i)

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


タイプの<、>、および=演算子を使用するだけで、それらが何であるかを知ることができません(これはあなたが意図したものではないかもしれません)
lhahne

コンテキストによっては、それらが意味をなさない場合や、正常に機能する場合があります。MyTypeについて詳しく知らないとわかりにくいので、おそらくユーザーは知っていますが、実際はそうではありません。
タンクタルス2009年

0

この質問には多くの素晴らしい答えがあります。また、テンプレートはオープンなデザインをサポートしていることにも言及する必要があります。オブジェクト指向プログラミング言語の現状では、このような問題を処理する場合はビジターパターンを使用する必要があり、真のOOPは複数の動的バインディングをサポートする必要があります。C ++のOpen Multi-Methods、P。Pirkelbauerなどを参照してください非常に興味深い読書のために。

テンプレートのもう1つの興味深い点は、実行時のポリモーフィズムにも使用できることです。例えば

template<class Value,class T>
Value euler_fwd(size_t N,double t_0,double t_end,Value y_0,const T& func)
    {
    auto dt=(t_end-t_0)/N;
    for(size_t k=0;k<N;++k)
        {y_0+=func(t_0 + k*dt,y_0)*dt;}
    return y_0;
    }

この関数Valueは、ある種のベクトル( std :: vectorではなく、呼び出す必要がある)のてください。std::dynamic_array混乱を避けるために

もし funcが小さい、この関数はインライン化から多くの利益を得ます。使用例

auto result=euler_fwd(10000,0.0,1.0,1.0,[](double x,double y)
    {return y;});

この場合、正確な答え(2.718 ...)を知っている必要がありますが、基本的なソリューションなしで単純なODEを構築するのは簡単です(ヒント:yで多項式を使用)。

これで、に大きな式がありfunc、ODEソルバーをさまざまな場所で使用しているため、実行可能ファイルはあらゆる場所でテンプレートのインスタンス化によって汚染されます。何をすべきか?最初に気付くことは、通常の関数ポインタが機能することです。次に、カリー化を追加して、インターフェースと明示的なインスタンス化を記述します

class OdeFunction
    {
    public:
        virtual double operator()(double t,double y) const=0;
    };

template
double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction& func);

しかし、上記のインスタンス化はに対してのみ機能しdoubleます。インターフェースをテンプレートとして記述しないでください。

template<class Value=double>
class OdeFunction
    {
    public:
        virtual Value operator()(double t,const Value& y) const=0;
    };

いくつかの一般的な値タイプに特化します。

template double euler_fwd(size_t N,double t_0,double t_end,double y_0,const OdeFunction<double>& func);

template vec4_t<double> euler_fwd(size_t N,double t_0,double t_end,vec4_t<double> y_0,const OdeFunction< vec4_t<double> >& func); // (Native AVX vector with four components)

template vec8_t<float> euler_fwd(size_t N,double t_0,double t_end,vec8_t<float> y_0,const OdeFunction< vec8_t<float> >& func); // (Native AVX vector with 8 components)

template Vector<double> euler_fwd(size_t N,double t_0,double t_end,Vector<double> y_0,const OdeFunction< Vector<double> >& func); // (A N-dimensional real vector, *not* `std::vector`, see above)

関数が最初にインターフェースを中心に設計されていた場合、そのABCからの継承を強制されることになります。これで、このオプションに加えて、関数ポインター、ラムダ、またはその他の関数オブジェクトがあります。ここで重要なのは、が必要でありoperator()()、その戻り値の型でいくつかの算術演算子を使用できる必要があることです。したがって、C ++に演算子のオーバーロードがない場合、この場合、テンプレートメカニズムは機能しなくなります。


-1

インターフェイスをインターフェイスから分離し、実装を交換できるという概念は、オブジェクト指向プログラミングに固有のものではありません。Microsoft COMのようなコンポーネントベースの開発で生まれたアイデアだと思います。(私のコンポーネントドリブン開発とは?)90年代になって初めて、「「実装」ではなく「インターフェース」へのプログラム」と「クラス継承」よりも「オブジェクト構成」を好むようになりました。(ちなみにどちらもGoFから引用されています)。

その後、Javaにガベージコレクターとinterfaceキーワードが組み込まれ、突然インターフェースと実装を実際に分離することが現実的になりました。あなたがそれを知る前に、アイデアはオブジェクト指向の一部になりました。C ++、テンプレート、およびSTLは、これらすべてに先行しています。


インターフェイスは単なるオブジェクト指向ではないことに同意します。しかし、型システムの能力ポリモーフィズムは(60年代のSimulaにありました)です。モジュールインターフェイスはModula-2とAdaに存在しましたが、これらは型システムで異なる動作をしたと思います。
アンディガビン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.