42

クラス設計に関するいくつかの事実、具体的には関数がメンバーであるべきかどうかについて戸惑いながら、私はEffective c++を調べて、アイテム23、つまりメンバー関数よりも非メンバー非フレンド関数を優先することを見つけました。Web ブラウザーの例で最初にそれを読むと、ある程度の意味がありましたが、その例の便利な関数 (本ではこのような非メンバー関数と呼ばれます) はクラスの状態を変更しますよね?

  • それでは、最初の質問です。

  • もう少し読んで、彼は STL 関数を検討します。実際、一部のクラスで実装されていない一部の関数は stl で実装されています。std::sort本のアイデアに従って、それらは、std::copyfromなどのいくつかの合理的な名前空間にパックされたいくつかの便利な関数に進化しますalgorithm。たとえば、vectorクラスには関数がなくsort、stlsort関数を使用するため、ベクトル クラスのメンバーではありません。assignしかし、メンバーとしてではなく便利な関数として実装できるように、同じ推論をベクトルクラスの他の関数に拡張することもできます。ただし、それは操作対象のソートなどのオブジェクトの内部状態も変更します。では、この微妙だが重要な (私が推測する) 問題の背後にある理論的根拠は何でしょうか。

もしあなたがその本にアクセスできるなら、これらの点をもう少し明確にしてもらえますか?

4

7 に答える 7

43

本へのアクセスは決して必要ではありません。

ここで扱っている問題は依存性再利用です。

適切に設計されたソフトウェアでは、変更が必要な場合に依存関係が克服しなければならないハードルであるため、依存関係を減らすために項目を互いに分離しようとします。

適切に設計されたソフトウェアでは、DRY原則 (Don't Repeat Yourself) を適用します。なぜなら、変更が必要な場合、何十もの異なる場所でそれを繰り返さなければならないのは苦痛であり、エラーが発生しやすいからです。

「古典的な」オブジェクト指向の考え方は、依存関係の処理がますます苦手になっています。クラスの内部に直接依存する非常に多くのメソッドを持つことにより、わずかな変更でも全体を書き直す必要があります。そうである必要はありません。

C++ では、STL (標準ライブラリ全体ではない) は、次の明確な目標を持って設計されています。

  • 依存関係の切断
  • 再利用を許可する

したがって、コンテナは、内部表現を隠す明確に定義されたインターフェイスを公開しますが、カプセル化された情報への十分なアクセスを提供して、アルゴリズムを実行できるようにします。不変条件が保証されるように、すべての変更はコンテナー インターフェイスを介して行われます。

たとえば、sortアルゴリズムの要件について考える場合。STL によって (一般的に) 使用される実装では、(コンテナから) 以下が必要です。

  • 特定のインデックスの項目への効率的なアクセス: ランダム アクセス
  • 2 つのアイテムを交換する機能: 非関連

したがって、ランダムアクセスを提供し、連想しないコンテナは、(理論的には) クイックソートアルゴリズムによって効率的にソートするのに適しています。

これを満たす C++ のコンテナは何ですか?

  • 基本的なC配列
  • deque
  • vector

そして、これらの詳細に注意を払えば、作成できるコンテナ

いちいち書き直し(コピー・ペースト・微調整)するsortのはもったいないですよね?

たとえば、std::list::sortメソッドがあることに注意してください。なんで ?std::listはランダム アクセスを提供しない (非公式にmyList[4]は機能しない) ため、fromsortアルゴリズムは適していません。

于 2011-05-13T11:25:44.180 に答える
20

私が使用する基準は、関数がメンバー関数であることによって大幅に効率的に実装できる場合、それはメンバー関数であるべきだというものです。::std::sortその定義を満たしていません。実際、外部実装と内部実装の効率の違いはまったくありません。

何かをメンバー (またはフレンド) 関数として実装することによる大幅な効率の向上は、クラスの内部状態を知ることで大きなメリットが得られることを意味します。

インターフェイス設計の技術の一部は、オブジェクトに対して実行する可能性のあるすべての操作を合理的に効率的に実装できるように、メンバー関数の最小セットを見つける技術です。また、このセットは、クラスで実行されるべきではない操作をサポートするべきではありません。したがって、たくさんのゲッター関数とセッター関数を実装して、それを良いと呼ぶことはできません。

于 2011-05-13T09:36:01.220 に答える
11

このルールの理由は、メンバー関数を使用することで、誤ってクラスの内部に依存しすぎてしまう可能性があるためだと思います。クラスの状態を変更することは問題ではありません。本当の問題は、クラス内のプライベート プロパティを変更する場合に変更する必要があるコードの量です。クラス (パブリック メソッド) のインターフェイスを可能な限り小さく保つことで、そのような場合に必要な作業量と、プライベート データで奇妙なことをしてインスタンスが一貫性のない状態になるリスクの両方を減らすことができます。 .

AtoMerZ も正しいです。非メンバー、非フレンド関数をテンプレート化して、他の型にも再利用できます。

ところで、Effective C++ のコピーを購入する必要があります。これは素晴らしい本ですが、この本のすべての項目に常に準拠しようとしないでください。オブジェクト指向設計は、優れた実践 (本などから) と経験 (どこかで効果的な C++ で書かれていると思います) の両方です。

于 2011-05-13T09:26:01.617 に答える
4

動機は単純です:一貫した構文を維持します。クラスが進化または使用されると、さまざまな非メンバーの便利な関数が表示されます。toUpperたとえば、クラスインターフェイスを変更して、文字列クラスのようなものを追加する必要はありません。( std::stringもちろん、の場合はできません。)スコットの心配は、これが発生すると、構文に一貫性がなくなることです。

s.insert( "abc" );
toUpper( s );

無料の関数のみを使用し、必要に応じてフレンドとして宣言することで、すべての関数の構文が同じになります。別の方法は、便利な関数を追加するたびにクラス定義を変更することです。

私は完全に確信しているわけではありません。クラスが適切に設計されている場合、そのクラスには基本的な機能があり、どの関数がその基本的な機能の一部であり、どの関数が追加の便利な関数であるか(存在する場合)はユーザーに明らかです。世界的には、文字列はさまざまな問題を解決するために使用されるように設計されているため、一種の特殊なケースです。これが多くのクラスに当てはまるとは想像できません。

于 2011-05-13T09:42:33.057 に答える
4

では、最初の質問ですが、彼らはメンバーになるべきではないでしょうか?

いいえ、これは続きません。慣用的な C++ クラス設計では (少なくとも、Effective C++で使用される慣用句では)、非メンバー非フレンド関数がクラス インターフェイスを拡張します。これらは、クラスへのプライベート アクセスを必要とせず、持っていないという事実にもかかわらず、クラスのパブリック API の一部と見なすことができます。この設計が OOP の定義によって「非 OOP」である場合、OK、慣用的な C++ はその定義によって OOP ではありません。

同じ推論を vector クラスの他の関数に拡張する

確かに、無料の関数である可能性のある標準コンテナーのメンバー関数がいくつかあります。たとえばvector::push_back、 の観点から定義されinsertており、クラスへのプライベート アクセスなしで確実に実装できます。ただし、その場合、はベクトルが実装push_backする抽象概念の一部です。BackInsertionSequenceこのような一般的な概念は、特定のクラスの設計を横断するため、独自の一般的な概念を設計または実装する場合、関数を配置する場所に影響を与える可能性があります。

確かに、おそらく異なるべきだった標準の部分があります。たとえば、std::string has way too many member functionsです。これらのクラスは、私たちが現在現代の C++ スタイルと呼んでいるスタイルに人々が実際に落ち着く前に設計されました。クラスはどちらの方法でも機能するため、違いを気にすることで得られる実用的なメリットはほとんどありません。

于 2011-05-13T10:31:06.673 に答える
1

ベクトルに限らず広く使われているため、sortはメンバ関数として実装されていないと思います。メンバー関数として持っていた場合、それを使用するコンテナーごとに毎回再実装する必要があります。なので、実装しやすいと思います。

于 2011-05-13T09:21:18.127 に答える