无法访问受保护的成员。

17

我不理解以下内容,当Derived继承自Base时,它可以通过派生类的函数访问其受保护成员。但是,如果Base类试图从Derived类中访问自己的成员(Derived本身允许访问Base),它就无法访问,为什么?

class Base {
protected:
    int x;
};

class Derived : Base {
public:
    void foo(Base* b);
};


void Derived::foo(Base* b) {
    b->x = 2;       // cannot access protected member,
                    // though Derived inherits from Base, why?
}

在你的例子中,Derived 没有访问它自己从 Base 继承而来的成员。它正在访问一些其他对象从 Base 继承而来的成员。 - Mooing Duck
还要注意,受保护的数据(与方法相反)很容易让你违反基类的不变量。 - Mark B
5个回答

15

一个常见的误解。

Derived::foo()内部,您可以访问类Derived对象的受保护基类成员。然而,*b并不是类型为Derived的对象。相反,它是类型为Base的对象,因此与您的类没有任何关系。

如果您将Derived*作为参数,则可以访问受保护的基类成员。


让我们详细说明一下:

struct Derived;

struct Base
{
  int f(Derived *);
protected:
  int x;
private:
  int y;
};

struct Derived : public Base
{
  int g(Base *);
  int h(Derived *);
};

int Derived::g(Base * b)
{
   return b->x; // error, protected member of unrelated class
   return b->y; // error, private member of different class
}

int Derived::h(Derived * d)
{
  return d->x;  // OK, protected base member accessible in derived class
  return d->y;  // error, private member of different class
}

int Base::f(Derived * d)
{
  return d->x;  // OK, d converts to Base*
  return d->y;  // OK, ditto
}

让我们来看看。如果没有继承,那么“protected”就和“private”一样了。所以“b->x”是私有的,Derived::g()没有访问权限。现在,有了继承,“protected”意味着Base子对象Derived内部是可访问的。然而,b->x并不是指向子对象,因为b是指向Base而不是指向Derived的指针。在Derived::h()中情况发生了变化,因为d->x确实指向子对象中的x,所以它是可以访问的。 - Kerrek SB
这帮助我现在理解了,当我思考它时,我认为如果b->x被保护起来,那么它也无法通过main()或全局访问。 - parc84

6
您碰到了标准中的一个特殊规则:
11.5 受保护的成员访问
当派生类的友元或成员函数引用基类的受保护非静态成员函数或受保护非静态数据成员时,除了在第11条款中描述的那些情况外,还要应用访问检查。*除了形成成员指针之外,*访问必须通过派生类本身(或从该类派生的任何类)的指针、引用或对象进行。
添加这个访问检查的原因之一是考虑到这些基类受保护成员的行为。由于成员是受保护的,不同的派生类可以向其中添加语义,甚至完全改变这些派生成员的含义。(这也是为什么受保护数据相当危险的原因之一)。由于您的类对其他派生类中对基类语义所做的添加/修改毫不知情,最好的方法就是在访问将通过基类时预防性地禁止访问基类成员。

1

为了提供别人所说的具体例子:

class Base {
protected:
  int x;
};

class Derived : Base {
public:
  void foo(Derived*, Base*);
};

int main() {
  Base fiddle;
  Derived fast, furious;
  fast.foo(&furious, &fiddle);
}

void Derived::foo(Derived *d, Base* b) {
  x = 1;       // Legal, updates fast.x
  this->x = 2; // Legal, updates fast.x
  d->x = 3;    // Legal, updates furious.x
  b->x = 4;    // Error, would have updated fiddle.x
}

0

你的想法是正确的,但你没有正确使用受保护的成员。

void foo(Base* b) 应该改为 void foo();

其实现应为:

void Derived::foo() { return this-> x; }

因为x是受保护的成员,所以你不能从另一个对象中访问它--即使该对象继承自该类。你只能从派生对象本身访问它。


你的例子与原帖的不同,而且在这种情况下具有误导性。 - Nicola Musatti
不完全正确。访问限制是针对类而不是实例的。 - Kerrek SB
1
除非他写了他想要的,而foo获取另一个Base*对象的指针。 - Mooing Duck
@Kerrek:我认为他的意思是 Derived1 : Base 无法访问 Derived2 : Base 的成员变量 x - Mooing Duck
@Moo:他说“从派生对象”;那是不正确的。 - Kerrek SB
@Nicola 当有人询问继承并向我展示一个将基类作为参数的“子类”代码时,我会认为他们不理解继承或受保护成员。鉴于此,我认为我的示例实际上是正确的,可以演示何时以及如何从子类访问受保护的成员。 - tjarratt

0
你所做的实质上是创建了一个基类的实例,它与派生类内部的基类实例具有不同的关系。将变量设置为 protected 可以让继承类访问其自己内部的基类实例。然而,在类中创建一个类型为 base 的对象是另一回事,因此不允许访问。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接