访问另一个子类中基类的受保护成员

Posted

技术标签:

【中文标题】访问另一个子类中基类的受保护成员【英文标题】:accessing a protected member of a base class in another subclass 【发布时间】:2012-07-22 19:47:51 【问题描述】:

为什么会编译:

class FooBase

protected:
    void fooBase(void);
;

class Foo : public FooBase

public:
    void foo(Foo& fooBar)
    
        fooBar.fooBase();
    
;

但这没有?

class FooBase

protected:
    void fooBase(void);
;

class Foo : public FooBase

public:
    void foo(FooBase& fooBar)
    
        fooBar.fooBase();
    
;

一方面,C++ 为该类的所有实例授予对私有/受保护成员的访问权限,但另一方面,它不为子类的所有实例授予对基类的受保护成员的访问权限。 这在我看来相当不一致。

我已经测试了使用 VC++ 和 ideone.com 进行编译,并且都编译了第一个但不是第二个代码 sn-p。

【问题讨论】:

@iammilind 你确定要关闭正确的问题吗?还是应该反过来? @Eiko 最初我用这个关闭了另一个。然后我发现那里的答案更加详尽,并且引用了标准。因此重新打开它并关闭它。 【参考方案1】:

foo 收到FooBase 引用时,编译器不知道该参数是否是Foo 的后代,因此它必须假设它不是。 Foo 可以访问其他Foo 对象的继承的受保护成员,而不是所有其他同级类。

考虑这段代码:

class FooSibling: public FooBase  ;

FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?

如果Foo::foo可以调用任意FooBase后代的protected成员,那么它可以调用与Foo没有直接关系的FooSibling的protected方法。这不是受保护的访问应该如何工作的方式。

如果Foo 需要访问所有FooBase 对象的受保护成员,而不仅仅是那些已知为Foo 后代的成员,那么Foo 需要成为FooBase 的朋友:

class FooBase

protected:
  void fooBase(void);
  friend class Foo;
;

【讨论】:

啊,对于您的示例代码,原因很明显,为什么不允许这样做。谢谢。 我不明白你的例子。如果 fooBase 不是虚拟的,那么你得到的是 FooBase::fooBase,这就是你所指出的。如果 fooBase 是虚拟的,您实际上是在调用 FooSibling::fooBase,但这也是您使用虚拟函数的原因:能够使函数适应实际对象?我看不出这种行为何时会成为问题。 虚拟在这里无关紧要,@Vincent。成员的虚拟身份不会影响谁可以使用其名称。成员的可见性 决定了谁可以使用其名称。 Foo 可以看到其他已知 Foo 对象的受保护成员。它看不到任何其他对象的受保护成员,甚至看不到与其祖先类相关的成员,因为这些祖先不必已知Foo。虚拟性和可见性是 C++ 中的正交概念(但不一定在其他语言中)。 @RobKennedy 我明白,我只是看不到任何需要阻止派生类从基类的另一个实例访问受保护的成员/函数的真实示例,无论是虚拟与否,。【参考方案2】:

C++ FAQ 很好地总结了这个问题:

[你]可以扒自己的口袋,但不能扒你父亲的口袋,也不能扒你哥哥的口袋。

【讨论】:

你可以挑选你自己和你儿子的口袋【参考方案3】:

关键是protected 允许您访问您自己的成员副本,而不是任何 其他对象中的那些成员。这是一个常见的误解,因为我们通常会概括并声明protected 授予成员对派生类型的访问权限(没有明确说明仅对他们自己的基类...)

现在,这是有原因的,通常您不应该访问层次结构的不同分支中的成员,因为您可能会破坏其他对象所依赖的不变量。考虑一种对某些大型数据成员(受保护)执行昂贵计算的类型和两种按照不同策略缓存结果的派生类型:

class base 
protected:
   LargeData data;
// ...
public:
   virtual int result() const;      // expensive calculation
   virtual void modify();           // modifies data
;
class cache_on_read : base 
private:
   mutable bool cached;
   mutable int cache_value;
// ...
   virtual int result() const 
       if (cached) return cache_value;
       cache_value = base::result();
       cached = true;
   
   virtual void modify() 
       cached = false;
       base::modify();
   
;
class cache_on_write : base 
   int result_value;
   virtual int result() const 
      return result_value;
   
   virtual void modify() 
      base::modify();
      result_value = base::result(); 
   
;

cache_on_read 类型捕获对数据的修改并将结果标记为无效,以便下一次 读取 值重新计算。如果写入次数相对较高,这是一个很好的方法,因为我们只按需执行计算(即多次修改不会触发重新计算)。 cache_on_write 预先计算结果,如果写入次数很少,并且您希望读取的成本具有确定性(考虑读取的低延迟),这可能是一个不错的策略。

现在,回到最初的问题。两种缓存策略都维护一组比基础更严格的不变量。在第一种情况下,额外的不变量是 cachedtrue 仅当 data 在最后一次读取后没有被修改。在第二种情况下,额外的不变量是result_value 始终是操作的值。

如果第三个派生类型引用base 并访问data 进行写入(如果protected 允许),那么它会破坏派生类型的不变量。

话虽如此,语言规范被破坏(个人意见),因为它留下了实现特定结果的后门。特别是,如果从派生类型的基类中创建指向成员成员的指针,则在derived 中检查访问,但返回的指针是指向base 成员的指针,可以应用于 任何base对象:

class base 
protected:
   int x;
;
struct derived : base 
   static void modify( base& b ) 
      // b.x = 5;                        // error!
      b.*(&derived::x) = 5;              // allowed ?!?!?!
   

【讨论】:

【参考方案4】:

在这两个示例中,Foo 都继承了一个受保护的方法 fooBase。但是,在第一个示例中,您尝试从同一类访问给定的受保护方法(Foo::foo 调用 Foo::fooBase),而在第二个示例中,您尝试从另一个未声明为朋友的类访问受保护方法类(Foo::foo尝试调用FooBase::fooBase,失败,后者被保护)。

【讨论】:

【参考方案5】:

在第一个示例中,您传递了一个 Foo 类型的对象,它显然继承了 fooBase() 方法,因此能够调用它。在第二个示例中,您试图调用受保护的函数,简单地说,无论在哪个上下文中,您都不能从其声明的类实例中调用受保护的函数。 在第一个示例中,您继承了受保护的方法 fooBase,因此您有权在 Foo 上下文中调用它

【讨论】:

【参考方案6】:

我倾向于从概念和信息的角度来看待事物。如果您的 FooBase 方法实际上被称为“SendMessage”并且 Foo 是“EnglishSpeakingPerson”并且 FooBase 是 SpeechPerson,那么您的 protected 声明旨在将 SendMessage 限制在 EnglishSpeakingPersons(和子类,例如:AmericanEnglishSpeakingPerson、AustralianEnglishSpeakingPerson)之间。另一种派生自SpeakingPerson 的FrenchSpeakingPerson 将无法接收SendMessage,除非您将FrenchSpeakingPerson 声明为朋友,其中“朋友”意味着FrenchSpeakingPerson 具有从EnglishSpeakingPerson 接收SendMessage 的特殊能力(即可以理解英语)。

【讨论】:

【参考方案7】:

除了hobo's answer,您还可以寻求解决方法。

如果您希望子类调用fooBase 方法,您可以将其设为static。具有所有参数的子类可以访问静态受保护方法。

【讨论】:

【参考方案8】:

你可以在没有这样的朋友的情况下工作......

class FooBase

protected:
    void fooBase(void);
    static void fooBase(FooBase *pFooBase)  pFooBase->fooBase(); 
;

这避免了必须将派生类型添加到基类。这似乎有点循环。

【讨论】:

以上是关于访问另一个子类中基类的受保护成员的主要内容,如果未能解决你的问题,请参考以下文章

在父类的子类中访问静态类的受保护成员

Java中的间接子类无法访问的超类中的受保护成员

如何防止基类的受保护成员仍然在子类的第二级被访问?

Java - 子类中的受保护变量是啥类型?

C#:基类中的受保护方法;无法使用来自另一个类的派生类对象进行访问[重复]

使用子类引用访问包外的受保护成员