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

Kai*_*udi 35 c++ inheritance encapsulation protected

为什么编译:

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

class Foo : public FooBase
{
public:
    void foo(Foo& fooBar)
    {
        fooBar.fooBase();
    }
};
Run Code Online (Sandbox Code Playgroud)

但这不是吗?

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

class Foo : public FooBase
{
public:
    void foo(FooBase& fooBar)
    {
        fooBar.fooBase();
    }
};
Run Code Online (Sandbox Code Playgroud)

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

我已经使用VC++和ideone.com测试了编译,并且编译了第一个但不是第二个代码片段.

Rob*_*edy 30

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

考虑以下代码:

class FooSibling: public FooBase { };

FooSibling sib;
Foo f;
f.foo(sib); // calls sib.fooBase()!?
Run Code Online (Sandbox Code Playgroud)

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

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

class FooBase
{
protected:
  void fooBase(void);
  friend class Foo;
};
Run Code Online (Sandbox Code Playgroud)

  • 啊,通过您的示例代码,不允许这样做的原因非常明显。谢谢。 (2认同)
  • @RobKennedy我知道,我只是看不到任何实际的示例,在该示例中,需要防止派生类从基类的另一个实例访问受保护的成员/函数,无论该实例是否为虚拟的。 (2认同)

h0b*_*0b0 20

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

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

  • 你可以自己挑选自己的儿子口袋 (9认同)

Dav*_*eas 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(); 
   }
};
Run Code Online (Sandbox Code Playgroud)

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

现在,回到原来的问题.两种缓存策略都保持一组比基数更严格的不变量.在第一种情况下,额外不变的是,cachedtrue只有在data还没有被最后一次读取后修改.在第二种情况下,额外不变量就是result_value操作的值始终.

如果第三个派生类型对a进行了引用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 ?!?!?!
   }
}
Run Code Online (Sandbox Code Playgroud)