私はC++ FAQを読んでいて、friend
宣言に興味がありました。個人的には使ったことはありませんが、言語を探求することに興味があります。
を使用する良い例は何friend
ですか?
FAQ をもう少し読んでみると、<<
>>
オペレーターがオーバーロードし、それらのクラスのフレンドとして追加するというアイデアが気に入っています。ただし、これがカプセル化を壊さない方法はわかりません。これらの例外が OOP の厳格さの範囲内にとどまることができるのはいつですか?
friend
まず(IMO)役に立たないと言う人の言うことを聞かないでください。それは便利です。多くの場合、一般公開を意図していないデータや機能を持つオブジェクトが存在します。これは、さまざまな領域に表面的にしか精通していない多くの作成者がいる大規模なコードベースに特に当てはまります。
フレンド指定子の代替手段はありますが、多くの場合、扱いにくい (cpp レベルの具体的なクラス/マスクされた型定義) か、絶対確実ではありません (コメントまたは関数名の規則)。
答えに。
指定子により、指定されたfriend
クラスは、フレンド ステートメントを作成するクラス内の保護されたデータまたは機能にアクセスできます。たとえば、以下のコードでは、誰でも子供に名前を尋ねることができますが、名前を変更できるのは母親と子供だけです。
Window などのより複雑なクラスを検討することで、この単純な例をさらに進めることができます。ほとんどの場合、Window には、公開してはならない多くの関数/データ要素がありますが、WindowManager などの関連クラスによって必要とされます。
class Child
{
//Mother class members can access the private parts of class Child.
friend class Mother;
public:
string name( void );
protected:
void setName( string newName );
};
職場では、コードのテストに友人を幅広く使用しています。これは、メインアプリケーションコードに適切なカプセル化と情報隠蔽を提供できることを意味します。ただし、友人を使用して内部状態とテスト用のデータを検査する個別のテストコードを作成することもできます。
私はあなたのデザインの本質的な構成要素としてfriendキーワードを使用しないと言っておけば十分です。
このfriend
キーワードには、いくつかの適切な用途があります。私がすぐに目にする2つの用途は次のとおりです。
フレンド定義では、クラス スコープで関数を定義できますが、関数はメンバー関数として定義されず、囲んでいる名前空間の自由な関数として定義され、引数依存のルックアップを除いて通常は表示されません。これにより、演算子のオーバーロードに特に役立ちます。
namespace utils {
class f {
private:
typedef int int_type;
int_type value;
public:
// let's assume it doesn't only need .value, but some
// internal stuff.
friend f operator+(f const& a, f const& b) {
// name resolution finds names in class-scope.
// int_type is visible here.
return f(a.value + b.value);
}
int getValue() const { return value; }
};
}
int main() {
utils::f a, b;
std::cout << (a + b).getValue(); // valid
}
ポリシーが派生クラスにアクセスする必要がある場合があります。
// possible policy used for flexible-class.
template<typename Derived>
struct Policy {
void doSomething() {
// casting this to Derived* requires us to see that we are a
// base-class of Derived.
some_type const& t = static_cast<Derived*>(this)->getSomething();
}
};
// note, derived privately
template<template<typename> class SomePolicy>
struct FlexibleClass : private SomePolicy<FlexibleClass> {
// we derive privately, so the base-class wouldn't notice that,
// (even though it's the base itself!), so we need a friend declaration
// to make the base a friend of us.
friend class SomePolicy<FlexibleClass>;
void doStuff() {
// calls doSomething of the policy
this->doSomething();
}
// will return useful information
some_type getSomething();
};
この回答には、その不自然な例があります。それを使用する別のコードは、この回答にあります。CRTP ベースは、データ メンバー ポインターを使用して派生クラスのデータ フィールドにアクセスできるように、そのポインターをキャストします。
@roo:クラス自体がプライベートメンバーにアクセスできるユーザーを決定するため、ここではカプセル化が壊れていません。カプセル化が破られるのは、これがクラスの外部から発生した場合、たとえばoperator <<
「私はクラスの友達です」と宣言した場合のみですfoo
。</ p>
friend
!public
の使用ではなく、の使用を置き換えます。private
実際、C++FAQはすでにこれに答えています。
標準的な例は、operator<< をオーバーロードすることです。もう 1 つの一般的な用途は、ヘルパーまたは管理者クラスに内部へのアクセスを許可することです。
C++ の仲間について聞いたガイドラインをいくつか紹介します。特に印象に残っているのはラストです。
編集: よくある質問をもう少し読んで、<< >> 演算子をオーバーロードし、これらのクラスのフレンドとして追加するというアイデアが好きですが、これがカプセル化を壊さない方法がわかりません
カプセル化をどのように破りますか?
データ メンバーへの無制限のアクセスを許可すると、カプセル化が解除されます。次のクラスを検討してください。
class c1 {
public:
int x;
};
class c2 {
public:
int foo();
private:
int x;
};
class c3 {
friend int foo();
private:
int x;
};
c1
明らかにカプセル化されていません。誰でもそれを読んで変更することができますx
。あらゆる種類のアクセス制御を強制する方法はありません。
c2
明らかにカプセル化されています。への公開アクセスはありませんx
。できることは、クラスに対して意味のある操作foo
を実行する関数を呼び出すことだけです。
c3
? それはあまりカプセル化されていませんか?への無制限のアクセスを許可しx
ますか? 未知の機能へのアクセスを許可しますか?
いいえ。クラスのプライベート メンバーにアクセスできる関数は1 つだけです。やったようc2
に。と同じようc2
に、アクセスできる関数は「ランダムな未知の関数」ではなく、「クラス定義にリストされている関数」です。と同じようc2
に、クラス定義を見るだけで、誰がアクセスできるかの完全なリストを確認できます。
では、これはどのようにカプセル化されていないのでしょうか? 同じ量のコードで、クラスのプライベート メンバーにアクセスできます。そして、アクセス権を持つ全員がクラス定義にリストされています。
friend
カプセル化を壊しません。「OOP」と言うとき、実際には「Java」を意味するため、一部の Java プログラマーは不快に感じます。彼らが「カプセル化」と言うとき、それは「private メンバーを任意のアクセスから保護する必要がある」という意味ではなく、「private メンバーにアクセスできる唯一の関数がクラス メンバーである Java クラス」という意味です。いくつかの理由。
まず、すでに示したように、制限が多すぎます。フレンド メソッドが同じことを許可されるべきではない理由はありません。
第二に、制限が不十分です。4 番目のクラスを考えてみましょう。
class c4 {
public:
int getx();
void setx(int x);
private:
int x;
};
前述の Java の考え方によれば、これは完全にカプセル化されています。 それでも、絶対に誰でも x の読み取りと変更を許可します。それはどのように理にかなっていますか?(ヒント:ありません)
結論: カプセル化とは、どの関数がプライベート メンバーにアクセスできるかを制御できるようにすることです。これらの関数の定義が正確にどこにあるかということではありません。
Andrew の例のもう 1 つの一般的なバージョン、恐ろしいコード対句
parent.addChild(child);
child.setParent(parent);
両方の行が常に一緒に実行され、一貫した順序で行われるかどうかを心配する代わりに、メソッドをプライベートにして、一貫性を強制するフレンド関数を使用できます。
class Parent;
class Object {
private:
void setParent(Parent&);
friend void addChild(Parent& parent, Object& child);
};
class Parent : public Object {
private:
void addChild(Object& child);
friend void addChild(Parent& parent, Object& child);
};
void addChild(Parent& parent, Object& child) {
if( &parent == &child ){
wetPants();
}
parent.addChild(child);
child.setParent(parent);
}
つまり、パブリック インターフェイスを小さく保ち、フレンド関数内のクラスとオブジェクトを横断する不変条件を適用できます。
フレンド アクセスを使用する便利な場所を見つけました: プライベート関数の単体テスト。
Private/Protected/Public 権限を使用して、メンバーと関数のアクセス権を制御しますか? したがって、これら 3 つのレベルのそれぞれのアイデアが明確であると仮定すると、何かが欠けていることが明らかになるはずです...
たとえば、保護されたメンバー/関数の宣言はかなり一般的です。あなたは、この機能が誰にとっても手の届かないところにあると言っています(もちろん、継承された子を除く)。しかし、例外はどうですか?どのセキュリティ システムでも、ある種の「ホワイト リスト」を持つことができますよね?
そのため、友人を使用すると、堅実なオブジェクトの分離を柔軟に行うことができますが、正当化されていると感じるものに対して「抜け穴」を作成することができます。
なくてもいいデザインは必ずあるので、必要ないと言う人もいると思います。グローバル変数の議論に似ていると思います: You should never use them. There are always a way to do without them... しかし実際には、それが (ほとんど) 最もエレガントな方法になる場合があります。 .. これは友達も同じだと思います。
設定関数を使用せずにメンバー変数にアクセスできること以外は、実際には何の役にも立たない
それは正確にはそれを見る方法ではありません。アイデアは、誰が何にアクセスできるかを制御することであり、設定機能の有無はほとんど関係ありません。
C++ の作成者は、それはカプセル化の原則を破るものではないと言っています。彼の言葉を引用します。
「友達」はカプセル化に違反していますか? いいえ、違います。「フレンド」は、メンバーシップと同様に、アクセスを許可するための明示的なメカニズムです。(標準準拠プログラムでは) ソースを変更せずにクラスへのアクセスを自分自身に許可することはできません。
クリア以上です...
Friend は、コンテナを構築していて、そのクラスのイテレータを実装したい場合に便利です。
簡単な答えは次のとおりです。実際にカプセル化が改善される場合は、 friendを使用してください。読みやすさと使いやすさの向上(演算子<<と>>は標準的な例です)も正当な理由です。
カプセル化を改善する例としては、他のクラスの内部と連携するように特別に設計されたクラス(テストクラスが思い浮かびます)が良い候補です。
私が以前働いていた会社で、友人を使ってまともな影響を与えたという興味深い問題が発生しました。私はフレームワーク部門で働き、カスタムOS上に基本的なエンジンレベルのシステムを作成しました。内部的にはクラス構造がありました:
Game
/ \
TwoPlayer SinglePlayer
これらのクラスはすべてフレームワークの一部であり、私たちのチームによって維持されていました。同社が制作したゲームは、Gamesの子供たちの1人から派生したこのフレームワークの上に構築されました。問題は、ゲームにはSinglePlayerとTwoPlayerがアクセスする必要のあるさまざまなものへのインターフェイスがありましたが、フレームワーククラスの外部に公開したくないということでした。解決策は、これらのインターフェースをプライベートにし、TwoPlayerとSinglePlayerが友情を介してそれらにアクセスできるようにすることでした。
正直なところ、この問題全体は、システムをより適切に実装することで解決できたはずですが、私たちは自分たちが持っていたものに縛られていました。
TDD を何度も実行するために、C++ で「friend」キーワードを使用しました。
友人は私のすべてを知ることができますか?
更新: Bjarne Stroustrup サイトから、「友人」キーワードに関するこの貴重な回答を見つけました。
「フレンド」は、メンバーシップと同様に、アクセスを許可するための明示的なメカニズムです。
キーワードをいつ/どこで使用するかについては非常に注意する必要があります。あなたのfriend
ように、私はめったに使用していません. 以下は、使用上の注意friend
と代替手段です。
2 つのオブジェクトを比較して、それらが等しいかどうかを確認したいとします。次のいずれかを実行できます。
最初のオプションの問題は、それが多くのアクセサーになる可能性があることです。これは、直接変数アクセスよりも (わずかに) 遅く、読みにくく、扱いにくいです。2 番目のアプローチの問題は、カプセル化を完全に破ることです。
クラスのプライベートメンバーへのアクセスを取得できる外部関数を定義できればいいのですが。friend
これは、次のキーワードで実行できます。
class Beer {
public:
friend bool equal(Beer a, Beer b);
private:
// ...
};
このメソッドは、とのプライベート メンバー ( 、 などの可能性があります)に直接equal(Beer, Beer)
アクセスできるようになりました。a
b
char *brand
float percentAlcohol
friend
== operator
注意すべき点がいくつかあります。
friend
はクラスのメンバ関数ではありませんpublic
!)friends
他の方法で行うのがはるかに難しい場合にのみ、私は実際に使用します。別の例として、、、、、 、 などfriends
の相互運用性のために、多くのベクトル数学関数が作成されることがよくあります。また、どこでもアクセサーを使用するよりも、友達になる方がはるかに簡単です。指摘したように、 (デバッグに非常に便利)やおそらく演算子に適用すると便利なことがよくありますが、次のような用途にも使用できます。Mat2x2
Mat3x3
Mat4x4
Vec2
Vec3
Vec4
friend
<<
>>
==
class Birds {
public:
friend Birds operator +(Birds, Birds);
private:
int numberInFlock;
};
Birds operator +(Birds b1, Birds b2) {
Birds temp;
temp.numberInFlock = b1.numberInFlock + b2.numberInFlock;
return temp;
}
私が言うように、私はまったくfriend
頻繁に使用するわけではありませんが、時々それはあなたが必要とするものです. お役に立てれば!
保護された関数をユニットテストするためにfriend-keywordのみを使用しています。保護された機能をテストするべきではないと言う人もいます。ただし、新しい機能を追加するときに、この非常に便利なツールを見つけました。
ただし、クラス宣言で直接キーワードを使用するのではなく、気の利いたテンプレートハックを使用してこれを実現します。
template<typename T>
class FriendIdentity {
public:
typedef T me;
};
/**
* A class to get access to protected stuff in unittests. Don't use
* directly, use friendMe() instead.
*/
template<class ToFriend, typename ParentClass>
class Friender: public ParentClass
{
public:
Friender() {}
virtual ~Friender() {}
private:
// MSVC != GCC
#ifdef _MSC_VER
friend ToFriend;
#else
friend class FriendIdentity<ToFriend>::me;
#endif
};
/**
* Gives access to protected variables/functions in unittests.
* Usage: <code>friendMe(this, someprotectedobject).someProtectedMethod();</code>
*/
template<typename Tester, typename ParentClass>
Friender<Tester, ParentClass> &
friendMe(Tester * me, ParentClass & instance)
{
return (Friender<Tester, ParentClass> &)(instance);
}
これにより、次のことが可能になります。
friendMe(this, someClassInstance).someProtectedFunction();
少なくともGCCとMSVCで動作します。
operator<< と operator>> に関しては、これらの演算子を友達にする正当な理由はありません。それらがメンバー関数であってはならないのは事実ですが、友達である必要もありません。
最善の方法は、パブリックの print(ostream&) および read(istream&) 関数を作成することです。次に、これらの関数に関して operator<< と operator>> を記述します。これにより、これらの関数を仮想化できるという追加の利点が得られ、仮想シリアライゼーションが提供されます。
C++ では、"friend" キーワードは、オペレーターのオーバーロードとブリッジの作成に役立ちます。
1.) 演算子のオーバーロードのフレンド キーワード: 演算子のオーバーロードの例: 2 つの float 変数"x" (x 座標用) と "y" (y 座標用)
を持つクラス "Point" があるとします。
ここで、"<<"
(抽出演算子) をオーバーロードして、呼び出す"cout << pointobj"
と x 座標と y 座標が出力されるようにする必要があります (pointobj はクラス Point のオブジェクトです)。これを行うには、次の 2 つのオプションがあります。
1.「ostream」クラスで「operator <<()」関数をオーバーロードします。 2.「Point」クラスで「operator<<()」関数をオーバーロードします。ここで、最初のオプションは適切ではありません。別のクラスに対してこの演算子を再度オーバーロードする必要がある場合は、「ostream」クラスを再度変更する必要があるためです。
"operator <<()"
関数を呼び出すことができます:1.ostream オブジェクト cout.As の使用: cout.operator<<(Pointobj) (フォーム ostream クラス)。
2. オブジェクトなしで呼び出します。As: operator<<(cout, Pointobj) (Point クラスから)。
Point クラスにオーバーロードを実装したためです。したがって、オブジェクトなしでこの関数を呼び出すには、"friend"
キーワードを追加する必要があります。オブジェクトなしでフレンド関数を呼び出すことができるからです。関数宣言は As:
"friend ostream &operator<<(ostream &cout, Point &pointobj);"
2.) ブリッジを作成する際のフレンド キーワード:
2 つ以上のクラスのプライベート メンバー (一般に "ブリッジ" と呼ばれる) にアクセスする必要がある関数を作成する必要があるとします。これを行う方法:
クラスのプライベート メンバーにアクセスするには、そのクラスのメンバーである必要があります。他のクラスのプライベート メンバーにアクセスするには、すべてのクラスでその関数をフレンド関数として宣言する必要があります。例: 2 つのクラス A と B があるとします。関数"funcBridge()"
は両方のクラスのプライベート メンバーにアクセスしたいと考えています。次に、両方のクラスが次のように宣言する必要があり"funcBridge()"
ます。
friend return_type funcBridge(A &a_obj, B & b_obj);
これは、フレンドキーワードを理解するのに役立つと思います。
TDD を何度も実行するために、C++ で「friend」キーワードを使用しました。
友人は私のすべてを知ることができますか?
いいえ、それは一方通行の友情です :`(
私が使用する特定のインスタンスの 1 つは、 Singletonクラスfriend
を作成するときです。このキーワードを使用すると、クラスに常に "GetInstance()" メソッドを使用するよりも簡潔なアクセサー関数を作成できます。friend
/////////////////////////
// Header file
class MySingleton
{
private:
// Private c-tor for Singleton pattern
MySingleton() {}
friend MySingleton& GetMySingleton();
}
// Accessor function - less verbose than having a "GetInstance()"
// static function on the class
MySingleton& GetMySingleton();
/////////////////////////
// Implementation file
MySingleton& GetMySingleton()
{
static MySingleton theInstance;
return theInstance;
}
ツリーの例は非常に良い例です。継承関係を持たずに、オブジェクトをいくつかの異なるクラスに実装しています。
コンストラクターを保護し、人々に「友達」の工場を強制的に使用させるためにも必要になるかもしれません。
...わかりました、率直に言って、それがなくても生きられます。
フレンド関数とクラスは、クラスのプライベートおよび保護されたメンバーへの直接アクセスを提供し、一般的なケースでカプセル化を壊さないようにします。ほとんどの使用は ostream で行われます: 次のように入力できるようにしたいと考えています:
Point p;
cout << p;
ただし、これには Point のプライベート データへのアクセスが必要になる場合があるため、オーバーロードされた演算子を定義します。
friend ostream& operator<<(ostream& output, const Point& p);
ただし、カプセル化には明らかな影響があります。まず、フレンド クラスまたは関数は、クラスのすべてのメンバーに完全にアクセスできるようになりました。第 2 に、クラスとフレンドの実装は、クラスの内部変更がフレンドを壊す可能性があるところまで絡み合っています。
友達をクラスの延長と見なす場合、論理的に言えば、これは問題ではありません。だが、それならば、そもそもなぜ仲間を突き出す必要があったのだろう。
「友人」が達成しようとしているのと同じことを達成するには、カプセル化を壊すことなく、次のようにします。
class A
{
public:
void need_your_data(B & myBuddy)
{
myBuddy.take_this_name(name_);
}
private:
string name_;
};
class B
{
public:
void print_buddy_name(A & myBuddy)
{
myBuddy.need_your_data(*this);
}
void take_this_name(const string & name)
{
cout << name;
}
};
カプセル化は壊れていません。クラス B は A の内部実装にアクセスできませんが、結果は、B を A のフレンドとして宣言した場合と同じです。コンパイラは関数呼び出しを最適化するため、結果は同じになります。直接アクセスとしての指示。
「友達」を使用することは、議論の余地のある利点を持つ単純なショートカットですが、明確なコストだと思います。
最も厳密で純粋なOOPの原則を順守し、どのクラスのデータメンバーにもアクセサーがないようにすることができます。そのため、すべてのオブジェクトは、間接メッセージを介してのみデータを操作できる唯一のオブジェクトである必要があります。つまり、メソッド。
ただし、C#にも内部可視性キーワードがあり、Javaにはいくつかの点でデフォルトのパッケージレベルのアクセシビリティがあります。C ++は、他のどのクラスと他のクラスだけがクラスを見ることができるかを正確に指定することにより、クラスの可視性の妥協を最小限に抑えることで、実際にはOOPの理想に近づきます。
私は実際にはC++を使用していませんが、C#に友人がいる場合は、実際によく使用するアセンブリグローバル内部修飾子の代わりにそれを使用します。.NETでの展開の単位はアセンブリであるため、カプセル化を実際に壊すことはありません。
ただし、クロスアセンブリフレンドメカニズムのように機能するInternalsVisibleTo Attribute(otherAssembly)があります。Microsoftは、これをビジュアルデザイナーアセンブリに使用しています。
異なるクラス (一方を他方から継承していない) が他方のクラスのプライベートまたは保護されたメンバーを使用している場合、フレンドシップを使用できます。
フレンド関数の典型的なユース ケースは、2 つの異なるクラス間で行われる操作で、両方のプライベートまたはプロテクト メンバーにアクセスする操作です。
http://www.cplusplus.com/doc/tutorial/inheritance/から。
非メンバー メソッドがクラスのプライベート メンバーにアクセスするこの例を見ることができます。このメソッドは、クラスのフレンドとしてこのクラスで宣言する必要があります。
// friend functions
#include <iostream>
using namespace std;
class Rectangle {
int width, height;
public:
Rectangle() {}
Rectangle (int x, int y) : width(x), height(y) {}
int area() {return width * height;}
friend Rectangle duplicate (const Rectangle&);
};
Rectangle duplicate (const Rectangle& param)
{
Rectangle res;
res.width = param.width*2;
res.height = param.height*2;
return res;
}
int main () {
Rectangle foo;
Rectangle bar (2,3);
foo = duplicate (bar);
cout << foo.area() << '\n';
return 0;
}
クラスのツリー アルゴリズムを実装するときに、教授から提供されたフレームワーク コードには、ノード クラスのフレンドとしてツリー クラスがありました。
設定関数を使用せずにメンバー変数にアクセスできるようにする以外は、実際には何の役にも立ちません。
友達はコールバックにも役立ちます。静的メソッドとしてコールバックを実装できます
class MyFoo
{
private:
static void callback(void * data, void * clientData);
void localCallback();
...
};
ここで内部的にcallback
呼び出し、インスタンスが含まれています。私の意見では、localCallback
clientData
また...
class MyFoo
{
friend void callback(void * data, void * callData);
void localCallback();
}
これにより、フレンドがcppで純粋にcスタイルの関数として定義され、クラスが乱雑にならないようにすることができます。
同様に、私がよく目にするパターンは、クラスの本当にプライベートなメンバーをすべて別のクラスに配置することです。このクラスは、ヘッダーで宣言され、cppで定義され、フレンドリングされます。これにより、コーダーは、クラスの複雑さと内部作業の多くをヘッダーのユーザーから隠すことができます。
ヘッダー内:
class MyFooPrivate;
class MyFoo
{
friend class MyFooPrivate;
public:
MyFoo();
// Public stuff
private:
MyFooPrivate _private;
// Other private members as needed
};
cppでは、
class MyFooPrivate
{
public:
MyFoo *owner;
// Your complexity here
};
MyFoo::MyFoo()
{
this->_private->owner = this;
}
ダウンストリームがこのように見る必要のないものを隠すことがより簡単になります。