クラスの設計、特に関数をメンバーにするかどうかについていくつかの事実に戸惑いながら、Effective c ++を調べたところ、項目23、つまり、メンバー関数よりも非メンバー非フレンド関数を優先することがわかりました。 Webブラウザーの例でそれを直接読むことは理にかなっていますが、その例の便利な関数(本ではこのような非メンバー関数と呼ばれています)はクラスの状態を変更しますね。
それで、最初の質問、彼らはメンバーになるべきではありませんか?
もう少し読んで、彼はSTL関数を検討し、実際、いくつかのクラスによって実装されていないいくつかの関数はstlに実装されています。この本のアイデアに従って、それらはalgorithm
のstd::sort
、std::copy
などのいくつかの合理的な名前空間にパックされたいくつかの便利な関数に進化します。たとえば、vector
クラスにはsort
関数がなく、stl sort
関数を使用するため、ベクトルクラスのメンバーではありません。しかし、同じ推論をassign
などのベクトルクラスの他の関数に拡張して、メンバーとしてではなく便利な関数として実装することもできます。ただし、これにより、オブジェクトが操作された並べ替えなど、オブジェクトの内部状態も変更されます。それで、この微妙だが重要な(私が推測する)問題の背後にある理論的根拠は何ですか。
この本にアクセスできる場合は、これらの点をもう少し明確にしていただけますか?
本へのアクセスは決して必要ではありません。
ここで扱っている問題は依存関係と再利用です。
適切に設計されたソフトウェアでは、依存関係を減らすためにアイテムを互いに分離しようとします。依存関係は、変更が必要なときに克服するためのハードルだからです。
適切に設計されたソフトウェアでは、[〜#〜] dry [〜#〜]の原則(Do n't Repeat Yourself)を適用します。これは、変更が必要な場合、苦痛を伴い、エラーが発生しやすいためです。十数か所でそれを繰り返さなければなりません。
「クラシック」OOマインドセットは、依存関係の処理がますます悪くなっています。クラスの内部に直接依存するメソッドをたくさん持つことで、わずかな変更は全体の書き直しを意味します。そう。
C++では、STL(標準ライブラリ全体ではない)は、次の明確な目標を持って設計されています。
したがって、コンテナは、内部表現を非表示にするが、アルゴリズムを実行できるようにカプセル化した情報への十分なアクセスを提供する、明確に定義されたインターフェイスを公開します。すべての変更は、不変条件が保証されるように、コンテナインターフェイスを介して行われます。
たとえば、sort
アルゴリズムの要件について考える場合です。 STLで(一般的に)使用される実装の場合、(コンテナーから)次のものが必要です。
したがって、ランダムアクセスを提供し、関連付けられていないコンテナは、(理論的には)クイックソートアルゴリズムによって効率的にソートするのに適しています。
これを満たすC++のコンテナは何ですか?
deque
vector
そして、これらの詳細に注意を払えば、yoのコンテナが書き込む可能性があります。
それらのそれぞれについてsort
を書き直す(コピー/貼り付け/微調整)のは無駄だと思いませんか?
たとえば、std::list::sort
メソッドがあることに注意してください。どうして ? std::list
はランダムアクセスを提供しないため(非公式にはmyList[4]
は機能しません)、したがってsort
fromアルゴリズムは適切ではありません。
私が使用する基準は、関数がメンバー関数であることによって大幅に効率的に実装できるかどうかです。その場合、それはメンバー関数である必要があります。 ::std::sort
はその定義を満たしていません。実際、外部と内部の実装で効率の違いはまったくありません。
メンバー(またはフレンド)関数として何かを実装することで効率が大幅に向上するということは、クラスの内部状態を知ることで大きなメリットが得られることを意味します。
インターフェイス設計の技術の一部は、オブジェクトに対して実行する可能性のあるすべての操作をそれらの観点から合理的に効率的に実装できるように、最小限のメンバー関数のセットを見つける技術です。また、このセットは、クラスで実行してはならない操作をサポートするべきではありません。したがって、一連のゲッター関数とセッター関数を実装して、それを適切に呼び出すことはできません。
このルールの理由は、メンバー関数を使用すると、誤ってクラスの内部に依存しすぎる可能性があるためだと思います。クラスの状態を変更することは問題ではありません。本当の問題は、クラス内のプライベートプロパティを変更する場合に変更する必要のあるコードの量です。クラスのインターフェース(パブリックメソッド)を可能な限り小さく保つことで、そのような場合に必要な作業量と、プライベートデータで何か奇妙なことをして、インスタンスが一貫性のない状態になるリスクの両方を減らすことができます。 。
AtoMerZも正しいです。非メンバー、非フレンド関数はテンプレート化して、他のタイプにも再利用できます。
ちなみに、Effective C++のコピーを購入する必要があります。これはすばらしい本ですが、この本のすべての項目に常に準拠しようとしないでください。オブジェクト指向設計は、(本などからの)優れた実践と経験(どこかでEffective C++でも書かれていると思います)の両方です。
動機は単純です:一貫した構文を維持します。クラスが進化または使用されると、さまざまな非メンバーの便利な関数が表示されます。たとえば、toUpper
のようなものを文字列クラスに追加するようにクラスインターフェイスを変更する必要はありません。 (の場合 std::string
もちろん、できません。)スコットの心配は、これが発生すると、構文に一貫性がなくなることです。
s.insert( "abc" );
toUpper( s );
無料の関数のみを使用し、必要に応じてフレンドと宣言することで、すべての関数の構文が同じになります。別の方法は、便利な関数を追加するたびにクラス定義を変更することです。
私は完全に確信しているわけではありません。クラスが適切に設計されていれば、基本的な機能があり、どの関数がその基本的な機能の一部であり、どの関数が追加の便利な関数であるか(存在する場合)はユーザーに明らかです。文字列は、さまざまな問題を解決するために使用されるように設計されているため、世界的には特殊なケースのようなものです。これが多くのクラスに当てはまるとは想像できません。
それで、最初の質問、彼らはより多くのメンバーであるべきではありませんか?
いいえ、これは続きません。慣用的なC++クラス設計(少なくとも、Effective C++で使用されるイディオム)では、非メンバーの非フレンド関数がクラスインターフェイスを拡張します。これらは、クラスへのプライベートアクセスを必要とせず、持っていないという事実にもかかわらず、クラスのパブリックAPIの一部と見なすことができます。この設計がOOPの定義によって「OOPではない」場合、OK、慣用的なC++はその定義によってOOP)ではありません。
同じ推論をベクトルクラスの他のいくつかの関数に拡張します
確かに、標準コンテナには、無料の関数である可能性のあるいくつかのメンバー関数があります。例えば vector::Push_back
はinsert
で定義されており、クラスへのプライベートアクセスなしで実装できます。ただし、その場合はPush_back
は、ベクトルが実装する抽象的な概念BackInsertionSequence
の一部です。このような一般的な概念は特定のクラスの設計にまたがっているため、関数を配置する場所に影響を与える可能性のある独自の一般的な概念を設計または実装している場合。
確かに、おそらく異なるはずの標準の部分があります。たとえば、 std :: stringのメンバー関数が多すぎます 。しかし、行われたことは行われ、これらのクラスは、人々が現在現代のC++スタイルと呼ばれるものに実際に落ち着く前に設計されました。クラスはどちらの方法でも機能するので、違いを心配することで得られる実用的なメリットはそれほど多くありません。
さまざまな考え:
friend
にすることができます。object.function(x, y, z)
表記が可能になります。これは、IMHOが非常に便利で、表現力があり、直感的です。また、多くのIDEの検出/完了機能との連携も優れています。メンバー関数と非メンバー関数としての分離は、クラスの本質的な性質、その不変条件と基本的な操作を伝達し、アドオンと場合によってはアドホックな「便利な」機能を論理的にグループ化するのに役立ちます。 TonyHoareの知恵を考えてみましょう。
「ソフトウェア設計を構築する方法は2つあります。1つは明らかに欠陥がないように単純にする方法、もう1つは明らかな欠陥がないように複雑にする方法です。方法ははるかに難しいです。」
非メンバー機能が高度に拡張されたり、追加の依存関係を取得したりすると、関数を個別のヘッダーや実装ファイル、さらにはライブラリに移動できるため、コア機能のユーザーは必要な部分の使用に対してのみ「料金」を支払うことになります。
(Omnifariousの答えは必読です。初めての場合は3回です。)
ソートはベクトルだけでなく広く使われているので、メンバー関数として実装されていないと思います。メンバー関数として持っている場合は、それを使用するコンテナーごとに毎回再実装する必要があります。ですから、実装を簡単にするためだと思います。