友情がC++で少なくともオプションで継承できないのはなぜですか?私は、明白な理由で推移性と反射性が禁止されていることを理解しています(これは単純なFAQ回答を引用する)だけを言いますが、virtual friend class Foo;
私を困惑させます。この決定の背後にある歴史的背景を知っている人はいますか?友情は本当に限られたハックでしたが、それ以来、いくつかのあいまいな立派な使用法になりましたか?
説明のために編集:私は次のシナリオについて話している、notAの子が公開されているBまたはBとその子の両方に。フレンド機能のオーバーライドなどへのアクセスをオプションで許可することも想像できます。
class A {
int x;
friend class B;
};
class B {
// OK as per friend declaration above.
void foo(A& a, int n) { a.x = n; }
};
class D : public B { /* can't get in A w/o 'friend class D' declaration. */ };
受け入れられた答え:as Loki states そのため、クラスまたは仮想メソッドの階層に友情を付与するための厳密なneedはありません。ボイラープレートプロキシ(友好的なベースが効果的になる)の必要性は嫌いですが、これはほとんどの場合誤用される可能性が高い言語メカニズムよりも望ましいと考えられていると思います。おそらく、Stroupstrupの The Design and Evolution of C++ を読んで読んだ時期だと思います。これらのタイプの質問に対するより良い洞察を得るために、お勧めします...
私はFoo
とその友人Bar
を書くことができるからです(したがって、信頼関係があります)。
しかし、Bar
から派生したクラスを作成する人々を信頼しますか?
あんまり。したがって、彼らは友情を継承してはなりません。
クラスの内部表現を変更するには、その表現に依存するものを変更する必要があります。したがって、クラスのすべてのメンバー、およびクラスのすべての友人も変更が必要になります。
したがって、Foo
の内部表現を変更する場合、Bar
も変更する必要があります(友情はBar
をFoo
に緊密にバインドするため)。友情が継承された場合、Bar
から派生したすべてのクラスもFoo
に緊密にバインドされるため、Foo
の内部表現が変更された場合は変更が必要になります。しかし、私は派生型の知識を持っていません(また、私はそうすべきではありません。それらは異なる会社などによって開発されることさえあります)。したがって、Foo
を変更することはできません。変更すると、コードベースに重大な変更が導入されるためです(Bar
から派生したすべてのクラスを変更できなかったため)。
したがって、友情が継承された場合、クラスを変更する機能に制限を誤って導入していることになります。これは、パブリックAPIの概念を基本的に役に立たなくするため、望ましくありません。
注:Bar
の子は、Foo
を使用してBar
にアクセスできます。Bar
のメソッドを保護するだけです。その後、Bar
の子は、親クラスを介して呼び出すことでFoo
にアクセスできます。
これは、あなたの望むことですか?
class A
{
int x;
friend class B;
};
class B
{
protected:
// Now children of B can access foo
void foo(A& a, int n) { a.x = n; }
};
class D : public B
{
public:
foo(A& a, int n)
{
B::foo(a, n + 5);
}
};
友情がC++で少なくともオプションで継承できないのはなぜですか?
あなたの最初の質問に対する答えはこの質問にあると思います:"あなたの父親の友人はあなたのプライベートにアクセスできますか?"
フレンドクラスは、アクセサ関数を介してフレンドを公開し、それらを介してアクセスを許可できます。
class stingy {
int pennies;
friend class hot_girl;
};
class hot_girl {
public:
stingy *bf;
int &get_cash( stingy &x = *bf ) { return x.pennies; }
};
class moocher {
public: // moocher can access stingy's pennies despite not being a friend
int &get_cash( hot_girl &x ) { return x.get_cash(); }
};
これにより、オプションの推移性よりも細かく制御できます。例えば、 get_cash
はprotected
であるか、実行時制限付きアクセスのプロトコルを強制する場合があります。
C++標準、セクション11.4/8
友情は継承も推移的でもありません。
友情が継承される場合、友だちを意図していないクラスが突然クラス内部にアクセスし、カプセル化に違反します。
ただ不要だからです。
friend
キーワードの使用法自体は疑わしいです。カップリングに関して言えば、それは最悪の関係です(継承と合成より先に)。
クラスの内部構造に変更を加えると、このクラスの友人に影響を与えるリスクがあります...不明な数の友人が本当に必要ですか?それらを継承する人が友人になる可能性がある場合、それらをリストすることさえできず、毎回クライアントコードを破る危険にさらされるでしょう、確かにこれは望ましくありません。
宿題/ペットプロジェクトでは、依存関係はしばしば遠い考慮事項であることを私は自由に認めます。小規模なプロジェクトでは重要ではありません。しかし、複数の人が同じプロジェクトに取り組み、これが数十万行に達するとすぐに、変更の影響を制限する必要があります。
これは非常に単純なルールをもたらします。
クラスの内部の変更は、クラス自体にのみ影響します
もちろん、あなたはおそらくその友人に影響を与えるでしょうが、ここには2つのケースがあります:
std::ostream& operator<<(...)
と思うが、これは純粋に言語規則の偶然によるメンバーではない簡単な方法の使用をお勧めします。
class Example;
class ExampleKey { friend class Example; ExampleKey(); };
class Restricted
{
public:
void forExampleOnly(int,int,ExampleKey const&);
};
この単純なKey
パターンを使用すると、実際に内部へのアクセスを許可せずに、友人を(ある意味で)宣言することができます。したがって、変更から分離できます。さらに、必要に応じて、この友人がそのキーを受託者(子供など)に貸すことができます。
簡単なロジック: '私は友人ジェーンがいます。昨日私たちが友人になったからといって、彼女の友人全員を私のものにしたわけではありません。」
私はまだそれらの個々の友情を承認する必要があり、それに応じて信頼のレベルはそうなります。
クラス内のフレンド関数は、関数にexternプロパティを割り当てます。つまり、externは、関数がクラス外のどこかで宣言および定義されたことを意味します。
したがって、フレンド関数はクラスのメンバーではありません。したがって、継承では、外部のものではなくクラスのプロパティのみを継承できます。また、フレンド関数の継承が許可されている場合、サードパーティのクラスが継承されます。
派生クラスは、ベースの「メンバー」である何かだけを継承できます。友達の宣言はnot友好クラスのメンバーです。
$ 11.4/1- "...友人の名前はクラスのスコープ内になく、友人は別のクラスのメンバーでない限り、メンバーアクセス演算子(5.2.5)で呼び出されません。"
$ 11.4-「また、フレンドクラスの基本句はそのメンバー宣言の一部ではないため、フレンドクラスの基本句は、フレンドシップを付与するクラスからプライベートメンバーおよび保護メンバーの名前にアクセスできません。」
そしてさらに
$ 10.3/7- "[注:仮想指定子はメンバーシップを意味するため、仮想関数は非メンバー(7.1.2)関数にはなりません。また、仮想関数呼び出しは特定のオブジェクト呼び出す関数を決定します。あるクラスで宣言された仮想関数は、別のクラスでフレンドとして宣言できます。] "
「友人」はそもそも基本クラスのメンバーではないので、派生クラスにどのように継承できますか?
友人はコンテナのスタイルインターフェイスのような継承が得意ですが、私にとっては、最初に言ったように、C++には伝播可能な継承がありません
class Thing;
//an interface for Thing container's
struct IThing {
friend Thing;
protected:
int IThing_getData() = 0;
};
//container for thing's
struct MyContainer : public IThing {
protected: //here is reserved access to Thing
int IThing_getData() override {...}
};
struct Thing {
void setYourContainer(IThing* aContainerOfThings) {
//access to unique function in protected area
aContainerOfThings->IThing_getData(); //authorized access
}
};
struct ChildThing : public Thing {
void doTest() {
//here the lack of granularity, you cannot access to the container.
//to use the container, you must implement all
//function in the Thing class
aContainerOfThings->IThing_getData(); //forbidden access
}
};
私にとってC++の問題は、どこからでもすべてのアクセスを制御するための非常に優れた粒度の欠如です。
friend Thingは、Thingのすべての子にアクセスを許可するfriend Thingになります。
さらに、正確なアクセスを許可する友人[名前付きエリア] Thing。*は、友人の特別な名前付きエリアを介してContainerクラスにあります。
夢を止めて。しかし今、あなたは友人の面白い使い方を知っています。
別の順序では、すべてのクラスが自己に友好的であることがわかっていることにも興味があります。つまり、クラスインスタンスはすべてを呼び出すことができます
制限のない同じ名前の別のインスタンスのメンバー:
class Object {
private:
void test() {}
protected:
void callAnotherTest(Object* anotherObject) {
//private, but yes you can call test() from
//another object instance
anotherObject)->test();
}
};
推測:クラスが他のクラス/関数をフレンドとして宣言する場合、その2番目のエンティティは最初のエンティティへの特権アクセスが必要だからです。 2番目のエンティティに、最初のエンティティから派生した任意の数のクラスへの特権アクセスを許可するのに、どのような用途がありますか?