在派生类中访问受保护成员

86

昨天我遇到了一个错误,虽然很容易解决,但我想确保我对C++的理解是正确的。

我有一个基类,其中包含一个受保护的成员:

class Base
{
  protected:
    int b;
  public:
    void DoSomething(const Base& that)
    {
      b+=that.b;
    }
};

这段代码可以成功编译并运行。现在我要继承 Base 类,但仍想使用 b 变量:

class Derived : public Base
{
  protected:
    int d;
  public:
    void DoSomething(const Base& that)
    {
      b+=that.b;
      d=0;
    }
};
请注意,在此情况下,DoSomething 仍然引用了一个 Base 而不是 Derived。我原以为我可以在 Derived 内部访问 that.b,但我收到了一个 cannot access protected member 错误(使用 MSVC 8.0 - 尚未尝试过 gcc)。
显然,在 b 上添加一个公共的 getter 方法解决了这个问题,但我想知道为什么我不能直接访问 b。我认为当你使用公有继承时,受保护的变量仍然对派生类可见。

3
请查看http://www.gotw.ca/gotw/076.htm(注意:不要在生产代码中使用该内容)。 - Brian
8个回答

65

一个类只能访问该类或派生类的实例的受保护成员,不能访问父类或表兄类实例的受保护成员。

在你的情况下,Derived 类只能访问 Derived 实例的 b 受保护成员,而不能访问 Base 实例的成员。

将构造函数更改为接受 Derived 实例将解决问题。


1
@AnishaKaul:你只能在自己的类型实例中访问基类的受保护成员,而不能在“堂兄弟”类型中访问。例如,Button无法从TextBox上的Control访问受保护的属性。 - SLaks
3
“将构造函数更改为接受一个派生类实例也会解决这个问题。”这句话的意思是,如果将构造函数修改为接受一个派生类的实例作为参数,同样可以解决这个问题。 - Mickael Bergeron Néron
1
@MickaelBergeronNéron:因为这样你将会在自己的类型上访问一个受保护的成员。 - SLaks
2
@SLaks 但是为什么基类的实例可以访问该基类其他实例的私有成员? - user3191398
2
在基类中,您可以定义一个虚拟的受保护访问器方法,从派生类中调用该方法时,可以传递对另一个基类或派生类实例的引用。这个想法怎么样? - turbopapero
显示剩余4条评论

12

protected成员可以通过以下方式访问:

  • 通过this指针
  • 或者即使在基类中声明,也可以访问相同类型的受保护成员
  • 或者来自友元类、函数

要解决您的问题,您可以使用最后两个选项之一。

在Derived::DoSomething中接受Derived或将Derived声明为Base的friend

class Derived;

class Base
{
  friend class Derived;
  protected:
    int b;
  public:
    void DoSomething(const Base& that)
    {
      b+=that.b;
    }
};

class Derived : public Base
{
  protected:
    int d;
  public:
    void DoSomething(const Base& that)
    {
      b+=that.b;
      d=0;
    }
};

在某些情况下,您可能还需要考虑公共getter方法。


8

如上所述,这只是语言的工作方式。

另一个解决方案是利用继承并传递给父方法:

class Derived : public Base
{
  protected:
    int d;
  public:
    void DoSomething(const Base& that)
    {
      Base::DoSomething(that);
      d=0;
    }
};

1
我在问题中举了一个不好的例子,但我不能调用Base :: DoSomething,因为当它进入Derived而不是Base时,DoSomething实际上会以不同的方式执行其任务。 - miked

4
你可以访问Derived的受保护成员,但不能访问Base的(即使它是从Base继承而来,作为Derived的受保护成员)。

1
这不起作用。考虑“您可以访问Derivedprivate成员。”以及对于继承的Baseprivate成员的影响。 - Ben Voigt

3
您可以尝试使用 static_cast< const Derived*>(pBase)->Base::protected_member ...
class Base
{
  protected:
    int b;

  public:
    ...
};

class Derived : public Base
{
  protected:
    int d;

  public:
    void DoSomething(const Base& that)
    {
      b += static_cast<const Derived*>(&that)->Base::b;
      d=0;
    }
    void DoSomething(const Base* that)
    {
      b += static_cast<const Derived*>(that)->Base::b;
      d=0;
    }
};

1
如果that的动态类型不是(cv)Derived,则其行为未定义 - xskxzr
但是,在这种情况下,b始终是基类的数据成员! - Martin.Bof
或者更好的是,你可以使用: b += static_cast<const Derived*>(that)->Base::b; - Martin.Bof

1
class Derived : public Base
{
  protected:
    int d;
  public:
    void DoSomething()
    {
      b+=this->b;
      d=0;
    }
};

//this will work

1

根据我写的stl黑科技,我写了一段小代码,似乎解决了在派生类中访问受保护成员的问题

#include <iostream>

class B
{
protected:
    int a;
public:
    void dosmth()
    {
        a = 4;
    }

    void print() {std::cout<<"a="<<a<<std::endl;}
};

class D: private B
{
public:
    void dosmth(B &b)
    {
        b.*&D::a = 5;
    }
};

int main(int argc, const char * argv[]) {

    B b;
    D d;
    b.dosmth();
    b.print();
    d.dosmth(b);
    b.print();

    return 0;
}


打印
a=4
a=5

事实证明,我的示例与上面发布的一个示例几乎相同。我也很惊讶它能够工作。据我看来,这是将其转换为派生类型,但我不确定。 - Stiv Zhops

-2

使用this指针访问受保护的成员

class Derived : public Base
{
  protected:
    int d;
  public:
    void DoSomething(const Base& that)
    {
      this->b+=that.b;
      d=0;
    }
};

1
这个答案是错误的。派生类无法访问基类的"b",因为它被声明为受保护的(这正是用户在第一次提问时所问的)。这段代码会产生编译器错误。 - tantuni
2
真是一个毫无意义的回答。问题出在 thatb 上。而且添加 this-> 不仅与此无关,而且完全没有作用,因为如果省略它,它是隐含的。我希望人们对一门语言有一点了解,并在发布答案之前测试他们编写的任何代码。 - underscore_d
这个可以工作,至少在Visual Studio 2019中是可以的。 - Tomasz Gawel
最好和最简单的答案。比上面所有的都要好用。我只想知道为什么我需要使用“this”来访问基类的受保护成员? - timoxd7

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