关于继承:为什么C ++不允许继承友谊?

Why does C++ not allow inherited friendship?

为什么友谊至少在C++中不可选择地继承?我明白传递性和反身性被禁止是有明显原因的(我这样说只是为了避免简单的常见问题引用答案),但是virtual friend class Foo;中缺少一些东西让我感到困惑。有人知道这个决定背后的历史背景吗?友谊真的只是一个有限的黑客,从那以后它已经找到了一些不知名的、值得尊敬的用途?

编辑澄清:我在谈论以下场景,而不是A的孩子与B或B及其孩子接触的场景。我还可以想象有选择地授予对友元函数等的覆盖访问权。

1
2
3
4
5
6
7
8
9
10
11
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. */ };

接受的答案:正如loki所说,通过在友好的基类中创建受保护的代理函数,可以或多或少地模拟这种效果,因此不需要将友谊授予类或虚拟方法继承权。我不喜欢使用样板代理(友好的基础实际上变成了样板代理),但我认为这比通常被误用的语言机制更可取。我想这可能是我购买和阅读Stroupstrup的C++设计和进化的时候,我已经看到这里有足够的人推荐,以更好地了解这些类型的问题……


因为我可以写信给Foo和它的朋友Bar(这样就有了信任关系)。

但是,我是否信任那些编写源自Bar类的类的人?不是真的。所以他们不应该继承友谊。

类的内部表示形式的任何更改都需要修改依赖于该表示形式的任何内容。因此,一个类的所有成员以及该类的所有朋友都需要修改。

因此,如果修改了Foo的内部表示,则还必须修改Bar(因为友谊将BarFoo紧密结合)。如果继承了友谊,那么从Bar派生的所有类也将与Foo紧密绑定,因此如果Foo的内部表示发生更改,则需要进行修改。但我不知道派生类型(我也不应该知道,它们甚至可能是由不同的公司开发的)。因此,我将无法更改Foo,因为这样做会将中断的更改引入代码库(因为我无法修改从Bar派生的所有类)。

因此,如果友谊是继承的,那么您会无意中引入对修改类的能力的限制。这是不可取的,因为您基本上使公共API的概念变得无用。

注:Bar的子代可以使用Bar访问Foo,只需使Bar中的方法受到保护即可。然后,Bar的子级可以通过其父类调用来访问Foo

这就是你想要的吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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);
        }
};


Why is friendship not at least optionally inheritable in C++?

我认为你第一个问题的答案就是这个问题:"你父亲的朋友可以接触到你的私人空间吗?"


友好类可以通过访问器函数公开其朋友,然后通过这些函数授予访问权限。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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++标准,第114/8节

Friendship is neither inherited nor transitive.

如果友谊是继承的,那么一个不应该是朋友的类会突然访问您的类内部,这违反了封装。


因为这是不必要的。

friend关键字的使用本身就是可疑的。就耦合而言,这是最糟糕的关系(远远领先于继承和组合)。

任何对类内部的更改都有可能影响到这个类的朋友…你真的想要一个未知数量的朋友吗?如果从他们那里继承的人也可能是朋友的话,你甚至不能列出他们,而且你会冒着每次都破坏客户代码的风险,当然这是不可取的。

我坦率地承认,对于家庭作业/宠物项目,依赖性常常是一个遥远的考虑因素。对于小型项目来说,这并不重要。但是,一旦有几个人在同一个项目上工作,而这个项目发展成成千上万行,您就需要限制变更的影响。

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

更改类的内部结构只应影响类本身。

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

    百万千克1无友函数:可能更多的是一个成员函数(我认为这里是std::ostream& operator<<(...),它不是纯粹由于语言规则的意外而成为的成员。百万千克1百万千克1朋友班?你不需要在真正的课堂上学习朋友课程。百万千克1

我建议使用简单的方法:

1
2
3
4
5
6
7
8
9
class Example;

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

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

这个简单的Key模式允许您声明一个朋友(以某种方式),而实际上不允许它访问您的内部,从而将它与变更隔离开来。此外,如果需要的话,它允许这个朋友把它的钥匙借给受托人(比如孩子)。


简单的逻辑:"我有一个朋友简。仅仅因为我们昨天交了朋友,她并不是所有的朋友都是我的。"

我仍然需要认可这些个人的友谊,信任程度也会相应地提高。


friend在继承方面很好,比如容器的样式接口但对我来说,正如第一个说法,C++缺少可传播继承性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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++的问题是缺少很好的粒度。控制从任何地方访问任何内容:

朋友的事可以成为朋友的事。*允许访问事物的所有子对象

还有,朋友[命名区域]的事.*允许访问在容器类中,通过朋友的特殊命名区域。

好吧,别做梦了。但是现在,你知道了朋友的一个有趣用法。

在另一个顺序中,你也可以发现有趣的是,所有的班级都对自己很友好。换句话说,类实例可以调用另一个同名实例的成员(无限制):

1
2
3
4
5
6
7
8
9
10
class Object {
     private:
         void test() {}
     protected:
         void callAnotherTest(Object* anotherObject) {
             //private, but yes you can call test() from
             //another object instance
             anotherObject)->test();
         }
};

类中的友元函数将extern属性分配给该函数。也就是说,extern意味着函数已经在类之外的某个地方声明和定义。

因此,它意味着friend函数不是类的成员。所以继承只允许您继承类的属性,而不是外部事物。另外,如果友元函数允许继承,则第三方类继承。


派生类只能继承基的"member"内容。友元声明不是友元类的成员。

$11.4/1-"...The name of a friend is
not in the scope of the class, and the
friend is not called with the member
access operators (5.2.5) unless it is
a member of another class."

$11.4 -"Also, because the base-clause
of the friend class is not part of its
member declarations, the base-clause
of the friend class cannot access the
names of the private and protected
members from the class granting
friendship."

进一步

$10.3/7-"[Note: the virtual specifier
implies membership, so a virtual
function cannot be a nonmember (7.1.2)
function. Nor can a virtual function
be a static member, since a virtual
function call relies on a specific
object for determining which function
to invoke. A virtual function declared
in one class can be declared a friend
in another class. ]"

因为"friend"一开始不是基类的成员,那么它如何能被派生类继承呢?


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