策略继承和无法访问的受保护成员

6
似乎即使类层次结构看起来正确,模板策略类的受保护成员也是无法访问的。
例如,对于以下代码片段:
#include <iostream>
using namespace std;

template <class T>
class A {
  protected:
    T value;
    T getValue() { return value; }
  public:
    A(T value) { this->value = value; }
};

template <class T, template <class U> class A>
class B : protected A<T> {
  public:
    B() : A<T>(0) { /* Fake value */ }
    void print(A<T>& input) {
      cout << input.getValue() << endl;
    }
};

int main(int argc, char *argv[]) {
  B<int, A> b;
  A<int> a(42);
  b.print(a);
}

编译器(在OS X上是clang,但gcc返回相同类型的错误)返回以下错误:
Untitled.cpp:18:21: error: 'getValue' is a protected member of 'A<int>'
      cout << input.getValue() << endl;
                ^
Untitled.cpp:25:5: note: in instantiation of member function 'B<int, A>::print' requested here
  b.print(a);
    ^
Untitled.cpp:8:7: note: can only access this member on an object of type 'B<int, A>'
    T getValue() { return value; }
      ^
1 error generated.

奇怪的是编译器的最后一条注释完全正确,但已经应用于类型为“B ”的对象“b”。这是编译器的错误还是代码存在问题?谢谢。

2
请查看此问题的答案:https://dev59.com/wmgu5IYBdhLWcg3wCCtK。 - Dan Nissenbaum
4个回答

10

你误解了protected访问权限的含义。

受保护的成员变量可以被派生类调用,但只能在包含它们的类内部基础对象上进行。

例如,如果我简化这个问题,使用:

class A {
protected:
    void getValue(){}
};

class B : protected A
{
public:
    void print(A& input)
    {
        input.getValue(); //Invallid
    }
};

除了类本身内部的"A"对象之外,无法在其他"A"对象上调用getValue方法。 例如,以下代码是有效的:

    void print()
    {
        getValue(); //Valid, calling the base class getValue()
    }

正如Dan Nissenbaum和shakurov指出的那样。然而,这也是有效的:

void print(B& input)
{
    input.getValue();
}

这是因为我们明确地声明输入是B对象。编译器知道所有B对象都具有对getValue的protected访问权限。在传递A&的情况下,该对象可能也是C类型,它可以从A派生而来并带有私有访问权限。


然而,通过将签名更改为void print(B& input)(即传递一个B对象,而不是一个A对象)- 这是合法的。(注意:假设getValue()返回有效值,而不是void)。 - Dan Nissenbaum
因为封装是基于类而不是基于对象的。否则编写赋值运算符将会是一场噩梦。 - shakurov
我认为答案由@shakurov给出,他写道A&输入可能不是对B类实例的引用。它可能是对另一个子类的引用(该子类可能无法访问getValue())。因此,只有实际显式的B类对象才允许访问其受保护成员。 - Dan Nissenbaum
如果去掉句子“But only on the base object contained inside the class itself”,我认为这个答案在技术上就是正确的。 - Dan Nissenbaum

6

让我们暂且忘记模板,看看这个:

 class A {
   protected:
     int value;
     int getValue() { return value; }
   public:
     A(int value) { this->value = value; }
 };

 class B : protected A {
   public:
     B() : A(0) { /* Fake value */ }
     void print(A& input) {
       cout << input.getValue() << endl;
     }
 };
print()方法实现错误,因为您不能在B中访问A的非公共成员。其原因是:从B内部,您只能访问B的非公共成员,这些成员可以继承或不继承,这并不重要。另一方面,A& input可能不是对B实例的引用,它可能是对另一个子类的引用(该子类可能无法访问getValue())。

你不能在B类中访问A类的非公共成员。 实际上可以。只要B类是从A类派生而来,你就能够在A类内部访问A类的受保护成员。 - David
@DavidJensen 我相信你所说的是shakurov所指的“在B内部”(即,对应于你提到的“仅在A类内部”)。 - Dan Nissenbaum
是的,但他仍然说它们无法访问,而我说可以。 - David
@DavidJensen 我理解这句话的意思是“你无法在B中访问显式A对象的非公共成员” - 你也是这样理解吗? - Dan Nissenbaum

3

派生类的成员函数可以在任何以其类型作为参数传递的对象中访问受保护的基类成员,只要传递的对象的明确声明类是派生类(或更进一步的派生类)

以基类类型显式传递的对象不能在派生类的成员函数中访问其受保护成员。

换句话说,如果我们有:

class A
{
protected:
    int x;
}

class B : public A
{
    void foo(B b)
    {
        b.x; // allowed because 'b' is explicitly declared as an object of class B
    }
    void goo(A a)
    {
        a.x; // error because 'a' is explicitly declared as having *base* class type
    }
};

如果参数的显式类型是 A,则不允许使用 a.x 这一行,因为受保护的访问规则仅适用于对象明确定义为与试图访问成员的类相同的类。 (... 或其派生类; 即,如果 class C 派生自 B,则将对象明确声明为类 C 对象将在 B 成员函数中也有 x 可访问。)

原因由 shakurov 给出,他写道(简述)

输入 A& 可能不是 B 的实例的引用。它可能是对另一个子类的引用(该子类可能无法访问 getValue())。

此答案的优秀解释也在这里提供:accessing a protected member of a base class in another subclass

有趣的是,我相信这来自C++标准:

11.4 Protected member access [class.protected] 1当非静态数据成员或非静态成员函数是其命名类(11.2)的受保护成员时,会应用除Clause 11之前描述的其他访问检查外的附加访问检查。如前所述,授予对受保护成员的访问权是因为引用出现在某个类C的友元或成员中。如果访问是为了形成成员指针(5.3.1),则嵌套名称指示符应指示 C 或从 C 派生的类。所有其他访问都涉及(可能隐式的)对象表达式(5.2.5)。在这种情况下,对象表达式的类应为 C 或从 C 派生的类。


2

不要被模板所分散注意力,它与错误无关。编译器报错的是在main函数中的一行代码,创建了一个类型为B<int, a>的对象并试图访问受保护的成员变量。无论类型如何,这都是不合法的。你只能从内部的成员函数或友元函数中使用受保护的成员。例如:

struct S {
protected:
    void f();
};

int main() {
    S s;
    s.f(); // error: attempts to call a protected member function
}

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