在C++ 11中,受保护意味着公众?

Ben*_*igt 13 c++ member member-access

继续学习C++中的错误:基本功能受到保护 ......

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;
}
Run Code Online (Sandbox Code Playgroud)

根据标准,这是否真的符合行为?

(我认为这是一个缺陷,并声称的类型&Gimme::i真的应该int Gimme::*,即使i是基类中的一员,但我没有看到在标准什么使得它如此,而且也显示这是一个非常具体的例子.)


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

这个漏洞颠覆了那份契约,完全反对了规则11.4p1的精神:

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


感谢AndreyT链接http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#203,它提供了激励变革的其他示例,并要求Evolution提出这个问题工作小组.


相关:GotW 76:访问权限的使用和滥用

AnT*_*AnT 11

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

如果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
}
Run Code Online (Sandbox Code Playgroud)

因为&Derived::m产生了int Base::*价值.如果它产生了一个int Derived::*值,代码将无法在第1行编译.如果我们试图修复它

  int Derived::*p = &Derived::m; // <- 1
Run Code Online (Sandbox Code Playgroud)

它将无法在第2行进行编译.使其编译的唯一方法是执行强制转换

  b.*static_cast<int Base::*>(p) = 42; // <- 2
Run Code Online (Sandbox Code Playgroud)

这不好.

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

04/00会议记录:

当前治疗的基本原理是允许最广泛地使用给定的成员地址表达.由于指向基本成员的指针可以隐式转换为指向派生成员的指针,因此使表达式的类型成为指向基本成员的指针,允许结果初始化或分配给指针 - to-base-member或指向派生的成员.接受此提议只允许后者使用.


bam*_*s53 5

关于C++中的访问说明符要记住的主要事情是它们控制名称的使用位置.它实际上没有做任何事情来控制对象的访问.在C++环境中"访问成员"意味着"使用名称的能力".

注意:

class Encapsulator {
  protected:
    int i;
};

struct Gimme : Encapsulator {
    using Encapsulator::i;
};

int main() {
  Encapsulator e;
  std::cout << e.*&Gimme::i << '\n';
}
Run Code Online (Sandbox Code Playgroud)

e.*&Gimme::i是允许的,因为它根本不访问受保护的成员.我们在访问中创建的成员Gimmeusing声明.也就是说,即使using声明并未暗示Gimme实例中的任何其他子对象,它仍然会创建一个额外的成员.成员和子对象不是同一个东西,并且Gimmie::i是一个独特的公共成员,可用于访问与受保护成员相同的子对象Encapsulator::i.


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

那个可以创建一个可访问的名称,或以其他方式提供对其他不可命名对象的访问,即使它与其他一些语言不同,也可能是令人惊讶的.