为什么C ++不允许inheritance友谊?

为什么友谊至less可以在C ++中inheritance? 我明白,由于显而易见的原因,传递性和反身性是被禁止的(我只是说这只是为了阻止简单的常见问题引用的答案),但是缺乏类似virtual friend class Foo; 困惑我。 有没有人知道这个决定背后的历史背景? 友谊真的只是一个有限的黑客,因为它已经发现了一些晦涩的可敬的用途?

编辑澄清:我在谈论下面的情况, 而不是 A的孩子暴露给B或B和它的孩子。 我也可以想象,可以select授予对朋友函数等的覆盖。

 class A { int x; friend class B; }; class B { // OK as per friend declaration above. void foo(A& a, int n) { ax = n; } }; class D : public B { /* can't get in A w/o 'friend class D' declaration. */ }; 

可以接受的答案:正如Loki所说 ,这个效应可以通过在友好的基类中进行保护代理函数来模拟,所以没有严格需要给予阶级或虚拟方法的友谊。 我不喜欢对样板代理(好友基地有效地成为代理)的需求,但是我认为这比在大多数情况下更可能被误用的语言机制更可取。 我认为这可能是我购买和阅读Stroupstrup的“C ++的devise和演变”(我已经看到足够多的人在这里推荐)来更好地洞察这些types的问题。

因为我可以写Foo和它的朋友Bar (因此有信任关系)。

但是,我相信编写从Bar派生的类的人吗?
不是真的。 所以他们不应该inheritance友谊。

一个类的内部表示的任何改变将需要对依赖于该表示的任何事情进行修改。 因此,class级的所有成员以及class级的所有朋友都需要修改。

因此,如果Foo的内部表示被修改,那么Bar也必须被修改(因为友谊将BarFoo紧紧地绑定在一起)。 如果友谊是inheritance的,那么所有从Bar派生出来的阶级也将与Foo紧密联系,因此如果Foo的内部表征发生了变化,就需要修改。 但我不知道衍生types(我也不应该,他们甚至可能由不同的公司开发)。 因此,我将无法更改Foo因为这样会引入对代码库的重大更改(因为我无法修改从Bar派生的所有类)。

因此,如果友谊被遗传了,那么你无意中引入了对修改类的能力的限制。 这是不可取的,因为你基本上渲染了一个公共API的概念。

注意: Bar一个小孩可以通过使用Bar来访问Foo ,只要使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) { ax = n; } }; class D : public B { public: foo(A& a, int n) { B::foo(a, n + 5); } }; 

为什么友谊至less可以在C ++中inheritance?

我想你的第一个问题的答案就是在这个问题上: “你父亲的朋友可以接触你的私人吗?

朋友们可以通过访问者函数来暴露自己的朋友,然后通过这些访问者授予访问权限。

 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关键字的使用本身是可疑的。 就耦合而言,这是最糟糕的关系(inheritance和组合之前的方式)。

对一个class级内部的任何改变都有可能影响到这个class级的朋友…你真的想要一个不知名的朋友吗? 如果那些从他们那里inheritance的人可能也是朋友,那么你甚至不能把他们列出来,而且你每次都会冒着破坏你的客户端代码的风险,这肯定是不可取的。

我坦率地承认,对于家庭作业/宠物项目来说,依赖往往是一个很远的考虑。 在小型项目上,这并不重要。 但是,只要有几个人在同一个项目上工作,就会变成几十条线,就需要限制变化的影响。

这带来了一个非常简单的规则:

改变一个类的内部只应该影响类本身

当然,你可能会影响它的朋友,但这里有两种情况:

  • 朋友免费function:可能更多的是一个成员函数无论如何(我认为std::ostream& operator<<(...)在这里,这不是纯粹意外的语言规则
  • 朋友课? 你不需要真正的课堂上的朋友class。

我会build议使用简单的方法:

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

这个简单的Key模式允许你声明一个朋友(在某种程度上),而不用实际的给它访问你的内部,从而隔离它的变化。 此外,如果需要的话,它可以让这个朋友把钥匙借给受托人(像孩子一样)。

猜测:如果一个类声明某个其他类/函数为朋友,那是因为第二个实体需要对第一个实体的特权访问。 在授予第二个实体有什么用处,从第一个实体获得任意数量的类的特权?

派生类只能inheritance基类的“成员”。 朋友的声明不是友谊class的成员。

$ 11.4 / 1-“…一个朋友的名字不在该类的范围内,并且这个朋友没有被成员访问操作符(5.2.5)调用,除非它是另一个类的成员。

$ 11.4 – “另外,由于friend类的基类不是它的成员声明的一部分,所以friend类的base子句不能访问授予友谊的类中的private和protected成员的名字。

并进一步

$ 10.3 / 7-“[注意:虚拟说明符意味着成员资格,所以虚拟函数不能是非成员函数(7.1.2)。虚拟函数也不能是静态成员,因为虚拟函数调用依赖于特定的对象确定要调用哪个函数。在一个类中声明的虚函数可以在另一个类中声明为一个朋友。]“

由于“朋友”首先不是基类的成员,那么派生类如何inheritance?

一个类中的Friend函数将extern属性赋给该函数。 即extern意味着函数已经被声明和定义在某个地方。

因此,这意味着朋友function不是一个类的成员。 所以inheritance只允许你inheritance一个类的属性而不是外部的东西。 而且,如果允许inheritance友元函数,则inheritance第三方类。

朋友对于容器的风格接口是很好的inheritance但是对于我来说,首先,C ++缺乏可传播的inheritance

 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(); } };