97

为什么友谊至少不能在 C++ 中选择性地继承?我理解传递性和反身性被禁止的原因很明显(我这样说只是为了阻止简单的常见问题解答引用答案),但是缺乏类似的东西让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. */ };

接受的答案:正如Loki 所说,可以通过在友元基类中创建受保护的代理函数或多或少地模拟效果,因此没有严格需要为类或虚拟方法层次结构授予友谊。我不喜欢样板代理的需要(友好的基础实际上变成了),但我认为这被认为比大多数时间更可能被滥用的语言机制更可取。我想我可能是时候购买并阅读 Stroupstrup 的The Design and Evolution of C++ 了,我在这里看到了足够多的人推荐它,以便更好地了解这些类型的问题......

4

10 回答 10

94

因为我可能会写Foo和它的朋友Bar(因此存在信任关系)。

但是我相信那些编写派生类的人Bar吗?
并不真地。所以他们不应该继承友谊。

类的内部表示的任何更改都需要对依赖于该表示的任何内容进行修改。因此类的所有成员以及类的所有朋友都需要修改。

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

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

注意: 的子级Bar可以Foo通过 using访问Bar,只需将方法置于Barprotected 中即可。然后 的子级可以通过其父类调用来Bar访问 a 。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);
        }
};
于 2010-08-25T00:45:50.043 回答
54

为什么友谊至少不能在 C++ 中选择性地继承?

我认为你第一个问题的答案就在这个问题中:“你父亲的朋友可以访问你的私人空间吗?”

于 2014-06-19T10:33:50.407 回答
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或可能实施运行时限制访问的协议。

于 2010-08-25T04:46:39.943 回答
9

C++ 标准,第 11.4/8 节

友谊既不是遗传的,也不是传递的。

如果友谊会被继承,那么一个本来不应该成为朋友的类会突然可以访问你的类内部,这违反了封装。

于 2010-08-25T00:59:02.207 回答
2

因为这只是不必要的。

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

对班级内部结构的任何更改都有可能影响该班级的朋友……您真的想要未知数量的朋友吗?如果从它们继承的人也可以成为朋友,您甚至无法列出它们,并且您每次都会冒着破坏客户代码的风险,这当然是不可取的。

我坦率地承认,对于家庭作业/宠物项目,依赖通常是一个遥远的考虑。在小型项目中,这并不重要。但是,一旦有几个人在同一个项目上工作并且这会增长到成千上万行,您就需要限制更改的影响。

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

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

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

  • 朋友免费功能:无论如何可能更像是一个成员功能(我认为std::ostream& operator<<(...)这里,它不是纯粹出于语言规则的意外而成为成员
  • 朋友班?你不需要真正的课程上的朋友课程。

我建议使用简单的方法:

class Example;

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

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

这种简单的Key模式允许您(以某种方式)声明一个朋友,而无需实际授予它访问您内部的权限,从而将其与更改隔离开来。此外,如果需要,它还允许该朋友将其密钥借给受托人(如儿童)。

于 2010-08-25T06:36:49.437 回答
0

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

于 2010-08-24T22:58:13.757 回答
0

派生类只能继承某些东西,即基类的“成员”。朋友声明不是交友类的成员。

$11.4/1- "...朋友的名字不在类的范围内,除非它是另一个类的成员,否则不会使用成员访问运算符 (5.2.5) 调用该朋友。"

$11.4 - “另外,因为朋友类的基子句不是其成员声明的一部分,朋友类的基子句不能从授予友谊的类中访问私有和受保护成员的名称。”

并进一步

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

既然“朋友”本来就不是基类的成员,那它怎么能被派生类继承呢?

于 2010-08-25T01:59:27.743 回答
0

类中的 Friend 函数将 extern 属性分配给该函数。即 extern 表示该函数已在类之外的某个地方声明和定义。

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

于 2014-11-26T12:23:00.397 回答
0

Friend 擅长继承类似容器的样式接口但对我来说,正如第一个说的那样,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(); 
         }
};
于 2016-12-08T17:00:32.840 回答
0

简单的逻辑:'我有一个朋友简。仅仅因为我们昨天成为朋友并不能让她所有的朋友都成为我的朋友。

我仍然需要批准这些个人友谊,信任程度也会相应提高。

于 2019-02-16T17:21:32.050 回答