【问题标题】:Policy inheritance and inaccessible protected members策略继承和不可访问的受保护成员
【发布时间】:2013-03-20 02:09:12
【问题描述】:

似乎无法访问模板策略类中的受保护成员,即使类层次结构看起来正确。

例如,使用以下代码 sn-p :

#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&lt;int, A&gt;',因此已经应用了。这是编译器错误还是代码有问题?

谢谢

【问题讨论】:

标签: c++ inheritance visibility protected policy


【解决方案1】:

您误解了受保护访问的含义。

受保护的成员可由派生类调用。但仅限于类本身包含的基础对象。

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

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

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

getValue 不能在类本身内部的“A”对象以外的“A”对象上调用。 例如,这是有效的。

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

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

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

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

【讨论】:

  • 但是,通过将签名更改为void print(B&amp; input)(即,传递B 对象,而不是A 对象)- 这是合法的。 (注意:假设getValue() 返回有效的东西,而不是void)。
  • 因为封装是基于类而不是基于对象的。否则编写赋值运算符将是一场噩梦。
  • 我认为答案是@shakurov 在写时给出的 A& 输入可能不是对 B 实例的引用。它可能是对另一个子类的引用(可能有() 不可访问。因此,只有实际显式的 B 类对象才允许访问其受保护的成员。
  • 我认为如果删除“但仅在类本身包含的基础对象上”这句话,这个答案在技术上是正确的。
【解决方案2】:

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

 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&amp; input 可能不是对B 实例的引用。它可能是对另一个子类的引用(很可能有 getValue() 无法访问)。

【讨论】:

  • "你不能在 B 中访问 A 的非公共成员。"是的你可以。只要 B 从 A 派生,您就可以访问 A 类的受保护成员,但只能在 A 类中。
  • @DavidJensen 我相信你所说的是 shakurov 所说的 inside B 的意思(即,对应于你提到的“仅在 A 类中”)。
  • 是的,但他仍然说他们无法访问,我说可以。
  • @DavidJensen 我读到这句话的意思是“你不能访问 B 内显式 A 对象的非公共成员”——这就是你的阅读方式?
【解决方案3】:

不要被模板分心。它与错误无关。编译器抱怨的main 中的行创建了B&lt;int, a&gt; 类型的对象并尝试访问受保护的成员。无论类型如何,这都是不合法的。您只能使用内部成员函数或友元函数的受保护成员。例如:

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

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

【讨论】:

    【解决方案4】:

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

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

    换句话说,如果我们有:

    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.x 行是不允许的,因为参数的显式类型是A,但受保护的访问规则仅适用于显式定义为same 类的对象作为试图访问成员的类。 (...或从它派生的类;即,如果class C派生自B,则传递显式声明为C 类的对象的对象也将在x 成员函数中访问x .)

    shakurov 在写作时给出了原因(释义)

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

    这里也给出了这个答案的一个很好的解释:accessing a protected member of a base class in another subclass。

    出于兴趣,我相信这来自这里的 C++ 标准:

    11.4 受保护的成员访问 [class.protected] 1 当一个 非静态数据成员或非静态成员函数是受保护的 其命名类 (11.2)115 的成员如前所述,访问 授予受保护成员,因为引用发生在朋友中 或某个类 C 的成员。如果访问是形成一个指向 成员(5.3.1),嵌套名称说明符应表示 C 或类 派生自 C。所有其他访问都涉及(可能是隐式的) 对象表达式(5.2.5)。在这种情况下,对象的类 表达式应为 C 或从 C 派生的类。

    【讨论】:

      猜你喜欢
      • 2023-03-23
      • 1970-01-01
      • 2012-09-26
      • 2011-05-24
      • 2016-06-12
      • 2014-06-09
      • 1970-01-01
      • 2017-09-26
      • 1970-01-01
      相关资源
      最近更新 更多