1

非メンバー関数がカプセル化を改善する方法の記事で、Scott Meyers は、非メンバー関数の "発生" を防ぐ方法はないと主張しています。

構文の問題

この問題について私が議論した多くの人と同じように、メンバー関数よりも非フレンド非メンバー関数を優先するべきであるという私のアドバイスの構文上の意味について留保する可能性があります。カプセル化についての議論。たとえば、Wombat クラスが食事と睡眠の両方の機能をサポートしているとします。さらに、食べる機能はメンバー関数として実装する必要がありますが、睡眠機能はメンバーまたは非友人非メンバー関数として実装できるとします。上記の私のアドバイスに従えば、次のように宣言できます。

class Wombat {
public:
   void eat(double tonsToEat);
   void sleep(double hoursToSnooze);
};

w.eat(.564);
w.sleep(2.57);

ああ、すべての均一性!しかし、この統一性は誤解を招きます。世界には、あなたの哲学が夢見るよりも多くの機能があるからです。

率直に言えば、非メンバー関数が発生します。ウォンバットの例を続けましょう。これらのフェッチする生き物をモデル化するソフトウェアを作成し、ウォンバットが頻繁に行う必要があることの 1 つは、正確に 30 分の睡眠であると想像してください。明らかに、コードに への呼び出しをw.sleep(.5)散らかすことはできますが、それを入力するには大量の .5 秒が必要であり、いずれにせよ、その魔法の値が変更されたらどうなるでしょうか? この問題に対処する方法はいくつかありますが、おそらく最も簡単なのは、実行したいことの詳細をカプセル化する関数を定義することです。あなたが Wombat の作成者ではないと仮定すると、関数は必ず非 memberでなければならず、そのように呼び出す必要があります。

void nap(Wombat& w) { w.sleep(.5); }

Wombat w;    
nap(w);

そして、あなたの恐ろしい構文の矛盾があります。ウォンバットに餌をやりたいときはメンバー関数呼び出しを行いますが、昼寝させたいときは非メンバー呼び出しを行います。

少し反省し、自分自身に正直であれば、すべてのクライアントが必要とするすべての機能を備えたクラスは存在しないため、使用するすべての重要なクラスと矛盾していると主張することを認めるでしょう。すべてのクライアントは、独自の便利な関数を少なくともいくつか追加します。これらの関数は常に非メンバーです。C++ プログラマーはこれに慣れており、何も考えていません。メンバー構文を使用する呼び出しもあれば、非メンバー構文を使用する呼び出しもあります。人々は、呼び出したい関数に適した構文を調べて、それらを呼び出します。人生は続く。これは特に、標準 C++ ライブラリの STL 部分で発生します。一部のアルゴリズムはメンバー関数 (例: size)、一部は非メンバー関数 (例: unique)、一部は両方 (例: find) です。誰もまばたきしない。あなたでさえありません。

太字/イタリック体の文で彼が言っていることに頭を悩ませることはできません。非メンバーとして実装する必要があるのはなぜですか? Wombat クラスから独自の MyWombat クラスを継承し、nap()関数を MyWombat のメンバーにしないのはなぜですか?

私は C++ を使い始めたばかりですが、おそらく Java でそれを行う方法です。これは C++ で行く方法ではありませんか? そうでない場合は、なぜですか?

4

3 に答える 3

1

新しいクラスと元の Wombat の間に非常に強い依存関係を作成しているためです。継承は必ずしも良いものではありません。これは、C++ の任意の 2 つのエンティティ間の 2 番目に強い関係です。friend宣言だけがより強力です。

Meyers が最初にその記事を公開したとき、私たちのほとんどは二刀流をとったと思いますが、今では一般にそれが真実であると認められています。現代の C++ の世界では、最初の本能はクラスから派生することではありません。実際に既存のクラス特殊化である新しいクラスを追加する場合を除き、派生は最後の手段です。

Javaでは事情が異なります。そこで継承します。他に選択肢はありません。

于 2015-03-01T21:29:19.187 に答える
1

Jerry Coffinが説明しているように、あなたのアイデアは全面的に機能しませんが、Wombatここのような階層の一部ではない単純なクラスでは実行可能です。

ただし、注意すべき危険がいくつかあります。

  • スライス- by 値を受け入れる関数がある場合、 の余分な付属物Wombatを切り取る必要がmyWombatあり、それらは元に戻りません。これは、すべてのオブジェクトが参照によって渡される Java では発生しません。

  • 基本クラス ポインター-Wombatが非ポリモーフィック (つまり、v テーブルがない) 場合は、コンテナー内で簡単に混在さWombatせることができないことを意味します。ポインタを削除しても、品種myWombatは正しく削除されません。(ただし、カスタム デリーターを追跡するものをmyWombat使用できます)。shared_ptr

  • 型の不一致: を受け入れる関数を記述した場合myWombat、それらを で呼び出すことはできませんWombat。一方、 a を受け入れるように関数を作成するとWombat、 のシンタックス シュガーを使用できませんmyWombat。キャストはこれを修正しません。あなたのコードは、インターフェースの他の部分と適切に相互作用しません。

これらすべての危険を回避する方法は、継承の代わりに包含を使用することです。にはプライベート メンバーがあり、公開したい Wombat プロパティの転送関数を記述します。これは、クラスの設計と保守という点でより多くの作業です。しかし、誰かがあなたのクラスを誤って使用する可能性を排除し、含まれているクラスがコピーできないなどの問題を回避することができます。myWombatWombatmyWombat


階層内のポリモーフィック オブジェクトの場合、型の不一致の問題は依然として存在しますが、スライシングと基本クラス ポインターの問題はありません。実際にはもっと悪いです。階層が次のようになっているとします。

Animal <-- Marsupial <-- Wombat <-- NorthernHairyNosedWombat

あなたはやって来て、から派生myWombatWombatます。ただし、これはNorthernHairyNosedWombatが の兄弟でありmyWombat、 の子であることを意味しWombatます。

したがって、追加したナイスシュガー関数はいずれにしmyWombatても使用できませNorthernHairyNosedWombatん。


要約: IMHO の利点は、後に残る混乱に値するものではありません。

于 2015-03-01T23:38:34.840 に答える