55

We all know members specified protected from a base class can only be accessed from a derived class own instance. This is a feature from the Standard, and this has been discussed on Stack Overflow multiple times:

But it seems possible to walk around this restriction with member pointers, as user chtz has shown me:

struct Base { protected: int value; };
struct Derived : Base
{
    void f(Base const& other)
    {
        //int n = other.value; // error: 'int Base::value' is protected within this context
        int n = other.*(&Derived::value); // ok??? why?
        (void) n;
    }
};

Live demo on coliru

Why is this possible, is it a wanted feature or a glitch somewhere in the implementation or the wording of the Standard?


From comments emerged another question: if Derived::f is called with an actual Base, is it undefined behaviour?

YSC
  • 38,212
  • 9
  • 96
  • 149
  • Comments are not for extended discussion; this conversation has been [moved to chat](https://chat.stackoverflow.com/rooms/167940/discussion-on-question-by-ysc-access-to-protected-member-through-member-pointer). –  Mar 31 '18 at 13:55
  • 2
    @YvetteColomb This was a collective effort to find a solution to the question/improve the question. Is there no way to put them back? There is still bits of info in them which could improve the accepted answer. – YSC Mar 31 '18 at 15:06
  • They're all still there in the linked chat. –  Mar 31 '18 at 23:18
  • 1
    that saved my day. Note that [the method `f` can be static](http://coliru.stacked-crooked.com/a/fe0968ad915efa71), which helps to avoid acutally ever creating a `Derived` object – 463035818_is_not_an_ai Jun 14 '19 at 15:07

4 Answers4

32

The fact that a member is not accessible using class member access expr.ref (aclass.amember) due to access control [class.access] does not make this member inaccessible using other expressions.

The expression &Derived::value (whose type is int Base::*) is perfectly standard compliant, and it designates the member value of Base. Then the expression a_base.*p where p is a pointer to a member of Base and a_base an instance of Base is also standard compliant.

So any standard compliant compiler shall make the expression other.*(&Derived::value); defined behavior: access the member value of other.

llllllllll
  • 16,169
  • 4
  • 31
  • 54
Oliv
  • 17,610
  • 1
  • 29
  • 72
15

is it a hack?

In similar vein to using reinterpret_cast, this can be dangerous and may potentially be a source of hard to find bugs. But it's well formed and there's no doubt whether it should work.

To clarify the analogy: The behaviour of reinterpret_cast is also specified exactly in the standard and can be used without any UB. But reinterpret_cast circumvents the type system, and the type system is there for a reason. Similarly, this pointer to member trick is well formed according to the standard, but it circumvents the encapsulation of members, and that encapsulation (typically) exists for a reason (I say typically, since I suppose a programmer can use encapsulation frivolously).

[Is it] a glitch somewhere in the implementation or the wording of the Standard?

No, the implementation is correct. This is how the language has been specified to work.

Member function of Derived can obviously access &Derived::value, since it is a protected member of a base.

The result of that operation is a pointer to a member of Base. This can be applied to a reference to Base. Member access privileges does not apply to pointers to members: It applies only to the names of the members.


From comments emerged another question: if Derived::f is called with an actual Base, is it undefined behaviour?

Not UB. Base has the member.

eerorika
  • 232,697
  • 12
  • 197
  • 326
-1

Just to add to the answers and zoom in a bit on the horror I can read between your lines. If you see access specifiers as 'the law', policing you to keep you from doing 'bad things', I think you are missing the point. public, protected, private, const ... are all part of a system that is a huge plus for C++. Languages without it may have many merits but when you build large systems such things are a real asset.

Having said that: I think it's a good thing that it is possible to get around almost all the safety nets provided to you. As long as you remember that 'possible' does not mean 'good'. This is why it should never be 'easy'. But for the rest - it's up to you. You are the architect.

Years ago I could simply do this (and it may still work in certain environments):

#define private public

Very helpful for 'hostile' external header files. Good practice? What do you think? But sometimes your options are limited.

So yes, what you show is kind-of a breach in the system. But hey, what keeps you from deriving and hand out public references to the member? If horrible maintenance problems turn you on - by all means, why not?

Bert Bril
  • 371
  • 2
  • 12
  • i find the first paragraph rather confusing. what else are access specifiers if not a way to "keep you from doing 'bad things'" ? Afaik in a correct program you could turn everything private into public and it still would be correct – 463035818_is_not_an_ai Jun 14 '19 at 15:17
  • I see all such things as tools. Type safety, const correctness, access specifiers - we know we can do without them (pick your language), but we can choose to use these tools. There are advantages, and disadvantages of each decision. What you are saying is that any choice to selectively and deliberately switch off will always be bad? Did you ever use a cast? – Bert Bril Jun 15 '19 at 23:48
-2

Basically what you're doing is tricking the compiler, and this is supposed to work. I always see this kind of questions and people some times get bad results and some times it works, depending on how this converts to assembler code.

I remember seeing a case with a const keyword on a integer, but then with some trickery the guy was able to change the value and successfully circumvented the compiler's awareness. The result was: A wrong value for a simple mathematical operation. The reason is simple: Assembly in x86 does make a distinction between constants and variables, because some instructions do contain constants in their opcode. So, since the compiler believes it's a constant, it'll treat it as a constant and deal with it in an optimized way with the wrong CPU instruction, and baam, you have an error in the resulting number.

In other words: The compiler will try to enforce all the rules it can enforce, but you can probably eventually trick it, and you may or may not get wrong results based on what you're trying to do, so you better do such things only if you know what you're doing.

In your case, the pointer &Derived::value can be calculated from an object by how many bytes there are from the beginning of the class. This is basically how the compiler accesses it, so, the compiler:

  1. Doesn't see any problem with permissions, because you're accessing value through derived at compile-time.
  2. Can do it, because you're taking the offset in bytes in an object that has the same structure as derived (well, obviously, the base).

So, you're not violating any rules. You successfully circumvented the compilation rules. You shouldn't do it, exactly because of the reasons described in the links you attached, as it breaks OOP encapsulation, but, well, if you know what you're doing...

The Quantum Physicist
  • 24,987
  • 19
  • 103
  • 189
  • 3
    I doubt OP is asking about practicalities – Passer By Mar 29 '18 at 08:38
  • 6
    Valiant effort. But this is a very long non-answer. Because I think the OP is wondering about what the *formal* wording on this is. – StoryTeller - Unslander Monica Mar 29 '18 at 08:38
  • I tried to explain why this works... Hopefully it helps. – The Quantum Physicist Mar 29 '18 at 08:39
  • *"an be calculated from an object by how many bytes there are from the beginning of the class"* - And what about multiple inheritance? – StoryTeller - Unslander Monica Mar 29 '18 at 08:39
  • @StoryTeller Every different case will have a different result. This depends on how the compiler structures the objects in bytes. The compiler eventually makes an order of all multiply inherited classes. – The Quantum Physicist Mar 29 '18 at 08:40
  • 5
    I think the issue is that your answer is valid for a specific architecture and/or implementation, but OP wants one in the general case, i.e. for the C++ abstract machine. – Rakete1111 Mar 29 '18 at 09:22
  • @Rakete1111 It's quite astonishing how people find it easy to down-vote answers they don't like, regardless of whether they're valid. I understand what you mean with the abstract machine point, but the issue the OP is talking about is a practical issue that needs a practical answer on how this works, and I tried to provide that. I don't think I deserve this much of attack from down-voters. – The Quantum Physicist Mar 29 '18 at 09:30
  • 6
    It's usually easier when the poster digs their heels in. A major criteria for voting is "usefulness". And while an answer with tangential elements *may* be useful, this one isn't that. It's just as Rakete1111 says, you focus on the aspect the OP didn't at all care much about. – StoryTeller - Unslander Monica Mar 29 '18 at 09:44
  • @TheQuantumPhysicist The question you're answering is "How does this work?" and not the question that was actually asked, "Why does this work?". It's a subtle distinction, but it's clear from the question that the asker does not have any confusion about how pointers to members work and is asking "why" on a deeper level than why it compiles and runs as expected. It is possible that the language could have specified that this kind of code is not well formed, but instead it specifies that it is legal. Therein lies the reason this question has been asked. – Jordan Melo Apr 02 '18 at 15:21