在c++ 11中,protected意味着public

In C++11, protected means public?

本文关键字:protected 意味着 public c++      更新时间:2023-10-16

继续c++错误:base function is protected…

c++ 11的指针到成员规则有效地去掉了protected关键字的任何值,因为受保护的成员可以在不相关的类中访问,而不需要任何邪恶/不安全的强制类型转换。

即:

class Encapsulator
{
  protected:
    int i;
  public:
    Encapsulator(int v) : i(v) {}
};
Encapsulator f(int x) { return x + 2; }
#include <iostream>
int main(void)
{
    Encapsulator e = f(7);
    // forbidden: std::cout << e.i << std::endl; because i is protected
    // forbidden: int Encapsulator::*pi = &Encapsulator::i; because i is protected
    // forbidden: struct Gimme : Encapsulator { static int read(Encapsulator& o) { return o.i; } };
    // loophole:
    struct Gimme : Encapsulator { static int Encapsulator::* it() { return &Gimme::i; } };
    int Encapsulator::*pi = Gimme::it();
    std::cout << e.*pi << std::endl;
}
  • http://ideone.com/pEXk9W
  • http://ideone.com/kKlTvD
  • http://ideone.com/zJX5U6

是否真的符合标准要求?

(我认为这是一个缺陷,并声称&Gimme::i的类型实际上应该是int Gimme::*,即使i是基类的成员。)但是我在标准中没有看到这样的内容,有一个非常具体的例子可以说明这一点。


我意识到有些人可能会对第三个注释方法(第二个ideone测试用例)实际上失败感到惊讶。这是因为考虑受保护的正确方式不是"我的派生类有访问权,其他人没有",而是"如果你从我派生,你将有权访问包含在你的实例中的这些继承变量,除非你授予它,否则其他人无权访问"。例如,如果Button继承了Control,那么Button实例中Control的受保护成员只能被ControlButton访问,并且(假设Button没有禁止)实例的实际动态类型和任何中间基。

这个漏洞颠覆了契约,完全违背了规则的精神。

当非静态数据成员或非静态成员函数是其命名类的受保护成员时,将应用第11条前面所述的额外访问检查。如前所述,对受保护成员的访问被授予,因为引用发生在某个类C 的友元或成员中。如果访问要形成指向成员(5.3.1)的指针,嵌套名称说明符应表示C或从C派生的类。所有其他访问都涉及(可能隐式的)对象表达式。在这种情况下,对象表达式的类应该是C或从C派生的类


感谢AndreyT的链接http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#203,它提供了更多的例子来激励改变,并呼吁由进化工作组提出这个问题。


同样相关的是:GotW 76:访问权的使用和滥用

我看到过这种技术,我称之为"受保护的黑客",在这里和其他地方提到过很多次。是的,这种行为是正确的,并且确实是一种绕过受保护访问而不诉诸任何"肮脏"黑客的合法方法。

m是类Base的成员时,使&Derived::m表达式产生Derived::*类型的指针的问题是类成员指针是逆变,而不是协变。这将使生成的指针不能用于Base对象。例如,以下代码编译

struct Base { int m; };
struct Derived : Base {};
int main() {
  int Base::*p = &Derived::m; // <- 1
  Base b;
  b.*p = 42;                  // <- 2
}

,因为&Derived::m产生int Base::*的值。如果它产生一个int Derived::*值,代码将在第1行编译失败。如果我们试图用

来修复它
  int Derived::*p = &Derived::m; // <- 1

,它将在第2行编译失败。使其编译的唯一方法是执行强制强制类型转换

  b.*static_cast<int Base::*>(p) = 42; // <- 2

这是不好的

注:我同意,这不是一个很有说服力的例子("只要从一开始就使用&Base:m,问题就解决了")。然而,http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#203有更多的信息,可以解释为什么最初做出这样的决定。他们国家

04/00会议记录:

当前处理的基本原理是允许最广泛的给定成员地址表达式的可能用法。自指向基的指针成员可以隐式地转换为指针到派生成员,使表达式的类型为a指向基成员的指针允许对结果进行初始化或赋值指向指向基的成员或指向派生的成员。接受这个建议将只允许后者使用。

关于c++中的访问说明符,要记住的主要事情是它们控制名称可以在哪里使用。它实际上不做任何事情来控制对对象的访问。在c++上下文中,"对成员的访问"意味着"使用名称的能力"。

观察:

class Encapsulator {
  protected:
    int i;
};
struct Gimme : Encapsulator {
    using Encapsulator::i;
};
int main() {
  Encapsulator e;
  std::cout << e.*&Gimme::i << 'n';
}

e.*&Gimme::i是允许的,因为它根本不访问受保护的成员。我们正在访问using声明在Gimme内部创建的成员。也就是说,即使using声明并不意味着在Gimme实例中有任何额外的子对象,它仍然会创建一个额外的成员成员和子对象不是一回事Gimmie::i是一个不同的公共成员,可以用来访问与受保护成员Encapsulator::i相同的子对象。


一旦理解了"类的成员"answers"子对象"之间的区别,就应该清楚,这实际上并不是11.4 p1规定的合同的漏洞或意外失效。

可以为不可命名的对象创建一个可访问的名称,或者以其他方式提供对不可命名对象的访问,这是预期的行为,尽管它与其他一些语言不同,可能令人惊讶。