首页 文章

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

提问于
浏览
34

为什么编译:

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

    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;
    };
    
  • 3

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

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

  • 30

    关键是 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 仅在 data 在上次读取后未被修改时才为 true . 在第二种情况下,额外不变量是 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 ?!?!?!
       }
    }
    
  • 10

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

  • 1

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

  • 1

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

  • 19

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

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

相关问题