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

时间:2012-07-24 13:20:48

标签: c++ inheritance encapsulation protected

为什么要编译:

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测试了编译,并且编译了第一个但不是第二个代码片段。

7 个答案:

答案 0 :(得分:30)

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

考虑以下代码:

class FooSibling: public FooBase { };

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

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

如果Foo需要访问所有FooBase个对象的受保护成员,而不仅仅是那些也称为Foo后代的对象,则Foo需要成为朋友FooBase

class FooBase
{
protected:
  void fooBase(void);
  friend class Foo;
};

答案 1 :(得分:20)

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

  

[你]可以自己掏腰包,但不允许你挑选父亲的口袋和兄弟的口袋。

答案 2 :(得分:10)

关键是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预先计算结果,如果写入次数很少,这可能是一个很好的策略,并且您需要读取的确定性成本(想想读取的低延迟)。

现在,回到原来的问题。两种缓存策略都保持一组比基数更严格的不变量。在第一种情况下,额外的不变量是cached只有在true在上次读取后未被修改时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 ?!?!?!
   }
}

答案 3 :(得分:3)

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

答案 4 :(得分:1)

在第一个示例中,您传递了一个Foo类型的对象,它显然继承了fooBase()方法,因此可以调用它。在第二个示例中,您试图调用受保护的函数,只是这样,无论在哪个上下文中,您都无法从声明它的类实例中调用受保护的函数。 在第一个示例中,您继承了受保护的方法fooBase,因此您有权将其称为WITHIN Foo上下文

答案 5 :(得分:1)

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

答案 6 :(得分:0)

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

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

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