In C++11, protected means public?

倖福魔咒の 提交于 2019-12-02 23:20:23

I have seen this technique, that I refer to as "protected hack", mentioned quite a few times here and elsewhere. Yes, this behavior is correct and it is indeed a legal way to circumvent protected access without resorting to any "dirty" hacks.

When m is member of class Base, then the problem with making the &Derived::m expression to produce a pointer of Derived::* type is that class member pointers are contravariant, not covariant. It would make the resultant pointers unusable with Base objects. For example, this code compiles

struct Base { int m; };
struct Derived : Base {};

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

because &Derived::m produces an int Base::* value. If it produced a int Derived::* value, the code would fail to compile at line 1. And if we attempted to fix it with

  int Derived::*p = &Derived::m; // <- 1

it would fail to compile at line 2. The only way to make it compile would be to perform a forceful cast

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

which is not good.

P.S. I agree, this is not a very convincing example ("just use &Base:m from the beginning and the problem is solved"). However, http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_closed.html#203 has more info that sheds some light on why such decision was made originally. They state

Notes from 04/00 meeting:

The rationale for the current treatment is to permit the widest possible use to be made of a given address-of-member expression. Since a pointer-to-base-member can be implicitly converted to a pointer-to-derived-member, making the type of the expression a pointer-to-base-member allows the result to initialize or be assigned to either a pointer-to-base-member or a pointer-to-derived-member. Accepting this proposal would allow only the latter use.

The main thing to keep in mind about access specifiers in C++ is that they control where a name can be used. It does not actually do anything to control access to objects. "access to a member" in the context of C++ means "the ability to use a name".

Observe:

class Encapsulator {
  protected:
    int i;
};

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

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

This, e.*&Gimme::i, is allowed because it does not access a protected member at all. We are accessing the member created inside Gimme by the using declaration. That is, even though a using declaration does not imply any additional sub-objects in Gimme instances, it still creates an additional member. Members and sub-objects are not the same thing, and Gimmie::i is a distinct public member that can be used to access the same sub-objects as the protected member Encapsulator::i.


Once the distinction between 'member of a class' and 'sub-object' is understood it should be clear that this is not actually a loophole or unintended failure of the contract specified by 11.4 p1.

That one can create an accessible name for, or otherwise provide access to, an otherwise un-nameable object is the intended behavior even though it is different from some other languages and may be surprising.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!