【问题标题】:Can a friend class object access base class private members on a derived class object?朋友类对象可以访问派生类对象上的基类私有成员吗?
【发布时间】:2017-01-16 00:00:43
【问题描述】:

我很惊讶下面的代码可以编译。

似乎与(公共继承的)基类友好的类可以访问基类的成员,前提是派生类的实例。

如果继承更改为private,则编译失败。

简而言之,d.b_varF::func(D& d) 内如何有效?

#include <iostream>
#include <string>
using namespace std;

class B{
    int b_var;
    friend class F;
};

class D: public B{
    int d_var;
};

class F{
    public:
        void func(D &d){
            d.b_var = 5; 
        }
};

int main()
{
    cout<<"fine";
}

【问题讨论】:

  • 我试图提高这个好问题的英语水平。如果我(无意中)毁了它,请拍拍我的手腕。
  • 我看不到这里的问题。 FB 的朋友班。没关系F可以访问B私密部分。
  • @BiagioFesta,这就是问题的答案。 OP没想到会这样。可能是因为访问说明符有点违反直觉。

标签: c++ inheritance private friend


【解决方案1】:

D 在使用公共继承时是 B。所以访问b_var 仍然是完全合法的。
但是,如果您尝试访问d_var,则会收到错误消息,因为您似乎知道,友谊本身并没有被继承。

继承总是使基的所有成员成为派生的成员。访问说明符仅影响标识符可见的位置。这就是为什么非法访问私有成员会产生与访问不存在的标识符不同的错误。

【讨论】:

  • b_var 默认为private,因此D 不应访问。
  • @GillBates,但尝试访问它的不是D,而是F。只要D 类型也是公开的B 类型,标识符对F 可见
  • 嗯,我认为对于基类的私有成员来说,继承甚至都不会打扰,哦,好吧,你每天都会学到新东西。
  • @GillBates,继承总是使基的所有成员成为派生的成员。访问说明符仅影响标识符可见的位置。这就是为什么非法访问私有成员会产生与访问不存在的标识符不同的错误。
  • @sameerasy 答案中已解释:D 也是B。并且B 部分已加为好友。
【解决方案2】:

你写:

似乎以某种方式继承了友谊,并且朋友类可以访问派生类的成员。

但它应该是:

似乎与(公共继承的)基类友好的类可以访问基类的私有成员,前提是派生类的实例。

或者:

似乎与另一个类成为朋友的类可以访问其实例的私有成员。

这与您的问题有关:

简而言之,d.b_varF::func(D&amp; d) 内如何有效?

因为d.b_var 是 B 类实例的成员(通过多态性),F 类的实例可以访问该实例(通过朋友状态)。

这不适用于d.d_var,因为与基类的友谊不会被继承,因此类 F 的实例无法访问 d 的私有成员。

这不适用于私有(或受保护)继承,因为由此添加了另一个“访问限制层”。此外,您还需要授予对派生类的私有继承成员的访问权限(d.b_var 然后是)。例如,让 D 成为 F 的朋友。

供参考:

【讨论】:

  • 不是对“简而言之,d.b_var 在 F::func(D&d) 中如何有效?”问题的答案。不过。
  • @GillBates 因为d 是其基类B(多态性)的一个实例,F 类的实例可以访问(通过friend-status)。
  • 然后将其添加到您的答案中(:
  • @moooeeeep 当然,但这就是我的意思:问题不在于“d 似乎不是 B 的实例”,而是它很明显 ,但禁止类作者指定访问 B 子对象。用户仍然可以看到该基地,但他们无法使用它。如果d 真的看起来不是B,那么错误将是没有已知的转换或类似的。在实践中,这是相当学术的,但我想我还是要指出来。
  • @underscore_d 感谢您指出这一点!我调整了答案以试图澄清这一点。
【解决方案3】:

虽然已经有很好的答案,但我认为一些图片在这里也会有所帮助。

这是您的B 类的抽象。 F 可以访问其所有成员。

当您现在实例化 D 对象时,它看起来像这样

它仍然是 B 对象,但也是 D 对象。可以这么说,它扩展了BF 仍然可以从 B 访问该部分,因为它仍然存在,但不能从 D 访问。

请注意,这些抽象并没有真正显示内存中的布局并解释覆盖等。但它们只是为了这个问题。

【讨论】:

  • 拜托,下次手写台词
  • 我总是靠图表出卖 :)
  • 是的,我总是更喜欢图片来解释继承和内存布局。但与文本相比,它们的工作量要大得多。 (是的,对图像质量差感到抱歉。;))
【解决方案4】:
  1. 似乎以某种方式继承了友谊,并且朋友类可以访问派生类的成员。
    简而言之,d.b_varF::func(D&amp; d) 内如何有效?

d.b_var 可能会产生误导。更准确地说(另一种看法),b_var 不是派生类D 的(直接)成员。相反,D 的对象包含基类B 的子对象,它有成员b_var,并且可以被朋友F 访问。 (我们也可以把d.b_var写成d.B::b_var。)

$10/3 Derived classes [class.derived]:

base-specifier-list 指定基类的类型 派生类类型的对象中包含的子对象。 [ 示例:

struct Base {
  int a, b, c;
};

struct Derived : Base {
  int b;
};

struct Derived2 : Derived {
  int c;
};

这里,Derived2 类的对象将有一个类的子对象 Derived 又将有一个 Base 类的子对象。 - 结尾 例子]

还有

  1. 如果继承更改为private,则编译失败。

因为

class B {
    int b_var;
    friend class F;
};

class D: private B {
    int d_var;
};

class F{
public:
    void func(D &d) {
        d.b_var = 5;  // Fail. Can't access subobject of B
        d.d_var = 5;  // Fail. Can't access member of D
    }
};

然后

class B {
    int b_var;
};

class D: private B {
    friend class F;
    int d_var;
};

class F{
public:
    void func(D &d) {
        d.b_var = 5;  // Fail. Can't access b_var of subobject of B
        d.d_var = 5;  // Fine.
    }
};

请注意,在最后一种情况下,即使F 是类D 的朋友,它也可以访问D 的所有私有/受保护成员,但不包括子对象B 中的成员,因为它们不是(直接)类D的成员。

【讨论】:

  • 很好地展示了与 D 的友谊会产生什么结果。它远不那么违反直觉,但与 OP 对比表明,同样的规则适用,因为它是访问说明符决定什么是可见的,什么是不是。
【解决方案5】:

class D 的对象由 2 个独立的部分组成:

part containing members of B 
part containing members of D

这就是为什么我们这样做时对象切片的概念起作用的原因:

D objD;
B objB = objD;

现在我们可以通过objBobject of class D 内部访问part containing members of B。编译器记住或可以区分class D 中的两个部分。所以编译器知道通过什么来访问什么。

friend class F; 内的 class B 语句只是告诉 member functions of class F 可以访问 class Bprivate, protected and public 成员。也就是说,对于member functions of class Fclass B 的所有成员都是public

实际上,在每个类中都包含三个可访问性部分:

public
protected
private 

所以当我们声明一些class B:

class B
{
    public:
        int a;
    protected:
        int b;
    public:
        int c;
};

然后在 B 类中创建以下 3 个部分,如上所示。

现在当我们声明一些class Ffriendclass B 时:

class B
{
    friend class F;
    private:
        int a;
    protected:
        int b;
    public:
        int c;            
};

然后编译器创建如下部分:

class B
{
    friend class F;
    private:
        int a;
    protected:
        int b;
    public:
        int c;
        //int a;  only for member functions of class F
        //int b;  only for member functions of class F             
};

请注意,int a;int b; 现在对于 member functionsclass F 是公开的。

现在,当 class Dclass B 派生出 publicly 时,class Bpublic 部分变为 class Dpublic 部分。同样,class Bprotected 部分变为 class Dprotected 部分。因此,class Bpublic 部分可以通过class D 的对象访问。由于B::a;B::b;members functions of class F 的公共部分中,因此B::aB::b 可以通过class D 的对象访问。另请注意,虽然int a;int b; 派生后成为class D 的成员,但编译器仍然能够区分它们并将它们视为part of class B

现在,当 class D 派生自 class Bprivately 时,class Bpublic 部分将变为 class Dprivate 部分。同样,class Bprotected 部分成为 class D 的受保护部分。因此,现在class B 内部的public 部分无法通过class D 的对象访问。回想一下,class BB::a;B::b; 最初在 members functions of class F 的公共部分,但在推导 private 之后,class B 的成员即 B::aB::b 现在在class D。因此,B::aB::b 不能通过class D 的对象访问。另请注意,虽然在推导 int a;int b; 之后成为 class D 的成员,但编译器仍然能够区分它们并将它们视为 part of class B。派生后class B的一些成员的可访问性和规则发生了变化。

由于这个问题在某种程度上与public, protected and private 推导的效果有关,因此为了完整性,请参阅: Why can a derived class not access a protected member of its base class through a pointer to base?

【讨论】:

  • 您的示例 B *pobjB = (B*)&amp;objD; 没有演示 object slicing
  • @moooeeeep :已编辑示例。前面的例子展示了成员在派生类对象中的适当部分正确排列的事实,这使得它们能够根据对象类型进行访问。
猜你喜欢
  • 1970-01-01
  • 2018-08-23
  • 2020-10-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-03
  • 2017-12-15
  • 2011-04-21
相关资源
最近更新 更多