18

一部のプログラマーは、「フレンド関数が C++ のカプセル化を破る」と言いました。また、一部のプログラマーは、「フレンド関数はカプセル化を破ることはありません。代わりに、カプセル化の障壁を自然に拡張します」と述べています。

どういう意味ですか?..

フレンド関数が C++ でカプセル化を破る場合、どのように??

4

10 に答える 10

25

C++ FAQからの引用は、友人とカプセル化の状況を非常によく説明していると思います。

いいえ!適切に使用すると、カプセル化が強化されます。

クラスを半分に分割する必要がある場合は、2 つの半分のインスタンス数またはライフタイムが異なる場合がよくあります。これらの場合、通常、2 つの半分は相互に直接アクセスする必要があります (2 つの半分は以前は同じクラスにあったため、データ構造に直接アクセスする必要があるコードの量は増えていません。単に再シャッフルしただけです)。コードを 1 つではなく 2 つのクラスに分けます)。これを実装する最も安全な方法は、2 つの半分を互いに友達にすることです。

上記のように友達を使用すると、プライベートなことをプライベートに保つことができます. これを理解していない人は、上記のような状況で友情を使わないように素朴な努力をすることが多く、実際にカプセル化を破壊することがよくあります. それらはパブリック データを使用するか (グロテスクです!)、パブリック get() および set() メンバー関数を介して半分の間でデータにアクセスできるようにします。プライベート データムのパブリック get() および set() メンバー関数を持つことは、プライベート データムがクラスの外部から (ユーザーの観点から) 「意味がある」場合にのみ OK です。多くの場合、これらの get()/set() メンバー関数は公開データとほぼ同じくらい悪いです: 非公開データの名前を (のみ) 隠しますが、非公開データの存在は隠しません。

于 2009-07-07T17:32:19.803 に答える
17

「友達」がカプセル化を破ると言う人がいる理由は、データと機能をカプセル化することの全体的なポイントは、そのデータを見る必要のある他の人が見ることができないようにするためですが、友達は他のクラスに内部を見せてしまうからです。

個人的には、クラスは完全に依存するのではなく、相互に依存する必要があるため、友人がカプセル化を破ることはないと思います。

車にたとえてみましょう。私たちは通常、これを抽象化のモデルとして使用し、アクセル ペダルを踏めばエンジンが動くことを知るために、エンジンがどのように機能するかを知る必要はないと言います。これがカプセル化の全体的な考え方であり、クラスとやり取りする必要がある関数だけを知る必要があります。

ただし、Mechanic クラスは車の特定の内部動作について知る必要があることは間違いありませんが、Mechanic を Car に組み込むことは意味がありません。

ここで友人の出番です。メカニックをエンジンやブレーキなど、修理が必要なものの友人にすれば、彼は修理することができます。ガス/ブレーキを押すことしかできない場合、彼はそれを修正できません。

于 2009-07-07T17:28:56.953 に答える
9

カプセル化とは、中身が見えないということであり、中身friend見えるということです。したがって、あなたの視点に応じて、カプセル化を破る (友人に中を見せることによって) か、またはカプセル化を拡張する (開発者が特定の友人だけに障壁を緩和させることによって) のいずれかです。friend

FWIW、良い経験則は、ネストされた/内部クラスのみがフレンドとして宣言されるべきであると言うことです。このルールは、「遠距離の友達は不可」と要約できます。つまり、クラスの友達 (存在する場合) は、クラス自体と同じヘッダー ファイル内で宣言する必要があります。

于 2009-07-07T17:27:14.103 に答える
7

Stroustrup のThe Design and Evolution of C++

2.10 The Protection Model、pg. 53.
[...]

友好関係の宣言は、ある保護ドメインが別の保護ドメインに読み取り/書き込み機能を付与するのと同様のメカニズムと見なされていました。これは、クラス宣言の明示的かつ特定の部分です。したがって、宣言が「カプセル化に違反している」という繰り返しの主張を、friend 無知と非 C++ 用語との混同の組み合わせ以外の何物でもないと見なすことはできませんでした。

于 2009-07-07T17:44:27.080 に答える
3

あなたには2つの異なる考え方があります。

最初のもの (Java & C# ...) :: 関数が「プライベート」領域にアクセスする必要があるかどうかに関係なく、クラスのパブリック領域にあるすべての関数をスローします。

2 番目のもの (C++ ...) :: このクラスが存続できるように必要な関数のみを提供し、作成された型の無関係なコレクションでより高いレベルの関数を提供します。

IMHO :: C++ は OOP の目標を明確に満たしています。

于 2009-07-07T17:26:09.300 に答える
3

C++ は非常に原始的なアクセス制御システムを備えていると考えることができます。すべてのメンバー (フレンドまたはメンバー関数) にアクセスできるか、パブリック メンバーのみにアクセスできます (その他すべて、... を除く)、または public および protected にアクセスできます。メンバー (...継承のための特別な規則)。

抽象における「カプセル化」も同様に基本的なアクセス制御システムですが、通常、「クラスの一部」であるコードには特権が与えられ、言語が許可するすべての方法でオブジェクトを操作できると言う点が異なります。「クラスの一部」ではないコードは特権がなく、おそらく公開されている、より小さなパブリック インターフェイスを使用する必要があります。

では、「友達」とは何ですか?このように考えると、メンバー関数ではない特権コードを書くためのものです。技術的な理由でメンバー関数にできないものがあるため、必要です。私がすぐに思いつくのは、LHS での変換が必要な演算子のオーバーロードと、std::swap のような標準アルゴリズム テンプレート関数の特殊化です (パブリック スワップ関数がある場合、それはpublic swap メソッドが存在しても害になる可能性は低いですが、インターフェイスを直交させたい人たちをサポートするのはいいことです)。

問題は、メンバー関数ではない特権コードを持つためにカプセル化を破るかということです。Java では、Java コードは明らかに「クラスの一部」であり、それがクラス定義にある場合は「クラスの一部」であり、そうでない場合はクラスの一部ではないため、おそらく「はい」と言うでしょう。

C++ では、メンバー関数の定義が最初からクラス定義に含まれている必要がないため、少し明確ではありません。違いはほとんどありません:

// Foo.h
class Foo;
void bar(Foo &);

class Foo {
    friend void bar(Foo &);
public:
    static void baz(Foo &);
};

// Foo.cpp
void bar(Foo &f) {
    // access private members of f
}
void Foo::baz(Foo &f) {
    // access private members of f
}

意味のある意味で bar は「カプセル化を破る」のに対し、baz は「カプセル化を維持する」と私は確信できません。わずかな実用主義であっても、bar と baz がまったく同じことを行っていることを示しています。唯一の違いは構文であり、カプセル化とは何の関係もありません。

一方、明らかに「フレンド」を使用して、カプセル化を完全に破ることができます。たとえば、フレンドと見なすことができる他のすべてのクラスに名前を付けることができます (Java ですべてのメンバーをパブリックにするか、パッケージを保護するか、C++ ですべてのメンバーをパブリックにするなど)。 )。「フレンド」は、メソッドだけでなくクラスと連携する必要があります。これにより、ネストされたクラスを必要に応じてフレンドとして宣言し、ネストされていない密結合クラスの小さなクラスターを持つことができます (つまり、境界を描画します)。単一クラスのカプセル化よりも高いレベルでのカプセル化)。これを使用してすべてのクラスを密結合すると、おそらく悪いコードになってしまいます。

これは実際には「友人」のせいではありません。とにかく「パブリック」として公開できたメンバーを公開するための新しい構文が与えられたからです。しかし、回避策として「友人」に手を伸ばし、それを使用して自分の不適切なパブリック インターフェイスを突破すると、「友人」は事実上、設計が不十分な言い訳になります。私は将来それを誓うかもしれませんし、他の人にも同様にアドバイスするかもしれませんが、これは私が二日酔いで目覚めるたびに飲酒を誓うのと同じ理由です. 他の人は、大きな副作用なしに利益を享受するのに十分なほど賢明または幸運かもしれません.

したがって、「友人」は、ポインター演算が行うように、それ自体でカプセル化を破ることができます。また、アルコールが午前 3 時に溝に落ちるのと同じように。ただし、注意して適切な設計を行えば、friend の特定の用途はカプセル化を破る必要はありません。そして、将来の雇用主になる可能性のある人がこれを読んでいる場合に備えて、私が飲むとき、私は通常二日酔いではありません;-)

最終的に問題は、私が知っているすべての言語で、クラスのパブリック インターフェイスが実装するすべてのインターフェイスのすべてのメンバーを組み込むという意味で、インターフェイスが継承のように動作することです。したがって、クラス インターフェイスは他のどのインターフェイスよりも大きくなります。「is-a」関係が OO の鍵であるため、これは悪いことではありません。さらに、インターフェースを公開したときに何が起こるか、つまり任意のクライアントがそれを使用している可能性によく対応しています。多くの設計が要求するものに自然に対応していません。これは、デフォルトでクラスが小さなインターフェースを持つだけでなく、「スーパーユーザー」に大きなインターフェースを提供することです。その代わりに、大きなインターフェイスがデフォルトであり、「サブユーザー」は退屈な部分に固執します。

したがって、フレンドは、「サブユーザー」インターフェースに影響を与えることなく、「スーパーユーザー」により大きなインターフェースを提供するための鈍いツールの 1 つです。ただし、それは非常に率直であるため、関連するクラスがすべて一緒に設計されている場合にのみ機能し、クラスがどのように結合されるべきかについての議論は、何が「カプセル化を破る」かについての意見の相違につながります。言語のアクセス レベルはカプセル化を強制するものではなく、特に C++ はマルチパラダイム言語であることに注意してください。そのため、C++ はプログラマーがカプセル化を実施するのを支援するためにいくらかの努力をしますが、プログラマーは依然として適切に設計し、独自のプログラミング原則に従って利用可能な機能を使用する必要があります。

于 2009-07-07T18:22:44.490 に答える
0

フレンド関数を宣言するとすぐに、指定子にアクセスします。したがって、クラスのカプセル化された詳細は、クラスの一部/メソッドではないそのフレンド関数からアクセスできるようになります。したがって、基本的に、内部データ構造とメソッドに対する制御の一部を、「信頼する」外部関数に委譲します。

于 2009-07-07T17:24:19.430 に答える
0

friend キーワードを使用すると、他のクラスは C++ のプライベートおよび保護されたデータ メンバーにアクセスできます。これはカプセル化を破っていると考える人もいれば、プライベート/保護されたデータへのアクセスを必要とする少数の外部クラスにカプセル化を拡張していると考える人もいます。「カプセル化を破る」とは、メンバーとメソッドが通常何らかの理由でプライベートであり、friend キーワードがそのカプセル化を破ることを意味します。

私が見たコードでは、friend キーワードは通常、カプセル化を破り、コードの品質を低下させますが、それがうまく使用されている場合もあります。

これまでに見たことがない場合は、クラスの友情と継承に関する簡単なチュートリアルを次に示します。

友情と継承

于 2009-07-07T17:22:53.940 に答える
0

友情は、ある程度のカプセル化を犠牲にして結束を促進します。カプセル化されたユニット (クラスとその仲間) はまだありますが、粒度は低くなります。しかし、別の方法としてクラスに特別な機能を追加する場合、それは必ずしも属しているとは限りません。友情を使用すると、クラスのまとまりを維持しながら、必要なアクセスを許可できます。カプセル化は、OOP の唯一の原則ではなく、最も重要な原則ですらありません。

于 2009-07-07T17:40:24.200 に答える
-2

個人データにアクセスできるすべての人に線を引くと、それはデータがカプセル化されている範囲です。

キーワードを使用する場合friend、その線はより多くのものを囲む必要があります。このように、カプセル化は、視点に応じて拡張または解除されます。

一般に、 の使用はお勧めしませんfriend。代わりに、プライベート データのアクセサーを追加することを検討してください。

于 2009-07-07T17:24:55.340 に答える