C ++が継承された友情を許可しないのはなぜですか?


93

C ++で友情が少なくともオプションで継承できないのはなぜですか?私は明白な理由で禁止されている推移性と反射性を理解しています(私はこれは単純なFAQの引用回答を回避するためだけに言っています)が、virtual friend class Foo;パズルの線に沿った何かの欠如は私を困惑させます。この決定の背後にある歴史的背景を知っている人はいますか?友情は本当に限定されたハックに過ぎなかったのか、それからいくつかのあいまいな立派な用途にその道を見つけましたか?

説明のために編集します。私は次のシナリオについて話しています。Aの子が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. */ };

受け入れ答え:としてロキ状態、効果はfriended基底クラスで保護されたプロキシ機能を作ることによって、多かれ少なかれシミュレートすることができるので、厳密なありません必要性クラスまたは仮想メソッド階層構造に友情を付与するためには。ボイラープレートプロキシ(フレンドベースが効果的になる)の必要性は嫌いですが、ほとんどの場合、誤用される可能性が高い言語メカニズムよりも好ましいと考えられていたと思います。StroupstrupのThe Design and Evolution of C ++を購入して読んだときだと思います。これらの種類の質問についてより深い洞察を得るために、ここで十分な人々が推奨しているのを見てきました...

回答:


93

私はFooその友人と書いている可能性があるためですBar(したがって、信頼関係があります)。

しかし、私は派生クラスを書く人々を信頼しBarますか?
あんまり。だから彼らは友情を継承すべきではない。

クラスの内部表現に変更を加える場合は、その表現に依存するものを変更する必要があります。したがって、クラスのすべてのメンバーとクラスのすべての友達も変更する必要があります。

したがって、の内部表現Fooが変更されている場合は、変更するBar必要があります(友情がにしっかりとバインドさBarれているためFoo)。友情が継承された場合、から派生したすべてのクラスBarも密接にバインドされるためFooFooの内部表現が変更された場合は変更が必要になります。しかし、私は派生型についての知識がありません(私はそうではありません。それらは別の会社などによって開発されることもあります)。したがって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);
        }
};

4
ビンゴ。それはすべて、クラスの内部構造を変更することによって引き起こされる可能性があるダメージを制限することです。
j_random_hacker 2010

率直に言って、私がこれについて本当に考えているのは、中間クライアントがラッパーメソッドを基になる制限付きアクセスクラスに提示することによって外部クラスへの制限されたインターフェイスとして機能するAttorney-Clientパターンです。正確なクラスではなく、他のクラスのすべての子がインターフェースを利用できると言うことは、現在のシステムよりもはるかに便利です。
ジェフ

@Jeff:内部表現をクラスのすべての子に公開すると、コードが変更できなくなります(内部メンバーにアクセスしたい人は、実際にはBarでなくても、Barから継承する必要があるだけなので、カプセル化も実際には無効になります)。
マーティンヨーク

@Martin:そうです、このスキームでは、Friendly Baseを使用して、Friendlyクラスに到達できます。これは、多くの(ほとんどではないにしても)ケースで単純なカプセル化違反になる可能性があります。ただし、フレンドベースが抽象クラスである状況では、派生クラスはすべて、それ自体の相互インターフェースを実装するように強制されます。このシナリオの「詐欺師」クラスがカプセル化を解除していると見なされるのか、要求された役割を適切に機能させようとしなかった場合にインターフェイスコントラクトに違反していると見なされるのかはわかりません。
ジェフ

@Martin:そうです、それは私が欲しい効果であり、実際にはすでに実際に使用されています。Aは実際にはいくつかの制限付きアクセスクラスZへの相互に友好的なインターフェースです。通常の弁護士/クライアントイディオムに関する一般的な不満は、インターフェースクラスAはボイラープレートコールラッパーをZに呼び出す必要があり、サブクラスへのアクセスを拡張するために、Aのボイラープレートは基本的にBのようなすべての基本クラスで複製する必要があります。インターフェイスは通常、モジュールが提供したい機能ではなく、他のモジュールの機能を表現します自分自身を使いたい。
ジェフ

48

C ++で友情が少なくともオプションで継承できないのはなぜですか?

あなたの最初の質問に対する答えはこの質問にあると思います:「あなたの父親の友人はあなたのプライベートにアクセスできますか?」


36
公平に言えば、その質問はあなたの父親について不安な人を引き起こします。。。
iheanyi

3
この答えの意味は何ですか?せいぜい怪しげなコメントかもしれませんが、
DeveloperChrisは

11

フレンドクラスは、アクセサ関数を介してフレンドを公開し、それらを介してアクセスを許可します。

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か、それを強制する場合があります。


@Hector:リファクタリングへの投票! ;)
Alexander Shukaev 2016

7

C ++標準、セクション11.4 / 8

友情は相続も継承もありません。

友情が継承されると、友だちではないクラスが突然クラスの内部にアクセスできるようになり、カプセル化に違反します。


2
QがAを「友だち」とし、BはAから派生します。BがAから友情を継承する場合、BはAのタイプであるため、技術的にはQのプライベートにアクセスできるAです。したがって、これは実際的な理由で質問に答えることはできません。
mdenton8 2015年

2

それは単に不必要だからです。

friendキーワードの使用自体が疑わしいです。カップリングに関しては、それは最悪の関係です(継承と構成よりも先)。

クラスの内部に何らかの変更を加えると、このクラスの友達に影響を与えるリスクがあります...本当に未知の数の友達が必要ですか?それらから継承する人も友達である場合、それらをリストすることさえできず、毎回クライアントのコードを壊す危険にさらされるでしょう、これは確かに望ましくありません。

宿題やペットのプロジェクトでは、依存関係は遠く離れた考慮事項であることが多いことを認めます。小さなプロジェクトではそれは重要ではありません。しかし、何人かの人が同じプロジェクトに取り組み、これが数万のラインに成長するとすぐに、変更の影響を制限する必要があります。

これは非常に単純なルールをもたらします:

クラスの内部を変更すると、クラス自体にのみ影響するはずです

もちろん、あなたはおそらくその友人に影響を与えるでしょうが、ここでは2つのケースがあります。

  • フレンドフリー関数:おそらくいずれにせよメンバー関数のほうが多い(私はstd::ostream& operator<<(...)ここでは、純粋に言語規則の偶然によるメンバーではないと思う)
  • 友達クラス?実際のクラスでは友達クラスは必要ありません。

私はシンプルな方法の使用をお勧めします:

class Example;

class ExampleKey { friend class Example; ExampleKey(); };

class Restricted
{
public:
  void forExampleOnly(int,int,ExampleKey const&);
};

この単純なKeyパターンを使用すると、実際に内部へのアクセスを許可せずに、友人を(ある方法で)宣言して、変更から分離することができます。さらに、必要に応じて、この友達が受託者(子供など)に鍵を貸すことを許可します。


0

推測:クラスが他のクラス/関数をフレンドとして宣言している場合、その2番目のエンティティには最初のエンティティへの特権アクセスが必要なためです。最初のクラスから派生した任意の数のクラスへの特権アクセスを2番目のエンティティに許可するには、どのような用途がありますか?


2
クラスAがBとその子孫に友情を与えたい場合、追加された各サブクラスのインターフェイスを更新したり、Bにパススルーボイラープレートを書かせたりすることを避けることができます。
ジェフ

@ジェフ:ああ、私はあなたの意図した意味を誤解しました。... Bから継承されたすべてのクラスへのアクセス権があることをあなたは想定していたと思いAます
Oliver Charlesworth 2010

0

派生クラスは、ベースの「メンバー」である何かだけを継承できます。フレンド宣言は親しいクラスのメンバーではありません

$ 11.4 / 1- "...友達の名前はクラスのスコープ内にありません。別のクラスのメンバーでない限り、友達はメンバーアクセス演算子(5.2.5)で呼び出されません。"

$ 11.4-「また、friendクラスのbase節はそのメンバー宣言の一部ではないため、friendクラスのbase節は、友情を与えるクラスのプライベートおよび保護されたメンバーの名前にアクセスできません。」

そしてさらに

$ 10.3 / 7- "[注:仮想指定子はメンバーシップを意味するため、仮想関数を非メンバー(7.1.2)関数にすることはできません。仮想関数呼び出しは特定のオブジェクトに依存するため、仮想関数を静的メンバーにすることもできません呼び出す関数を決定します。あるクラスで宣言された仮想関数は、別のクラスでフレンドと宣言できます。]]

そもそも 'friend'は基本クラスのメンバーではないので、派生クラスに継承するにはどうすればよいですか?


友情は、メンバーのような宣言を通して与えられますが、他のどのクラスが「実際の」メンバーの可視性の分類を本質的に無視できるかの通知ほど、実際にはメンバーではありません。引用した仕様セクションでは、これらのニュアンスとフレームの動作に関して言語がどのように動作するかを自己矛盾のない用語で説明していますが、異なる方法で作成されている可能性があり、残念ながら上記の根拠はありません。
ジェフ

0

クラスのFriend関数は、関数にexternプロパティを割り当てます。つまり、externは、関数がクラス外のどこかで宣言および定義されていることを意味します。

したがって、Friend関数はクラスのメンバーではありません。そのため、継承では、外部のものではなく、クラスのプロパティのみを継承できます。また、フレンド関数の継承が許可されている場合は、サードパーティのクラスが継承します。


0

友人はコンテナのスタイルインターフェイスのような継承に優れていますが、私にとって、最初に言ったように、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 ++の問題は、どこからでもすべてのアクセスを制御するための非常に優れた細分性の欠如です。

フレンドシングは、フレンドシングになることができます。*シングのすべての子へのアクセスを許可する

さらに、フレンド[名前付きエリア] 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(); 
         }
};

0

単純な論理:「私には友達のジェーンがいる。昨日友達になったからといって、すべての友達が私のものになるわけではありません。」

私はまだそれらの個々の友情を承認する必要があります、そして信頼のレベルはそれに応じてでしょう。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.