Accessing a member variable's address in derived class.Behavior change when member is having different access specifier
Asked Answered
H

3

11

I have a base class A and derived class B

class B is derived from A as public

I want to access the member variable's address if A is class a is member variable

I am observing different behavior when i am using protected and public access specifier.

When member a of class A is protected in this case i am getting:

cout<<&A::a << endl; throwing me an compiler error..

but cout<<&(A::a)<<endl; is valid and giving me proper result.

and When member a of class A is public in this case i am getting:

Why this behavior?

Here is the full code :

#include <iostream>
using namespace std;

class A
{
    protected:
    int a;
    void fun() 
    {
    cout<<"A's fun"<<endl;
    }

public:
    A(int Aarg):a(Aarg)
    {
        cout << "A's construction done" <<endl;
    }
};


class B: public A
{
protected:
int b;
public:
void fun()
{
cout << "B's fun"<<endl;
}

B(int As, int Bs):A(As), b(Bs)
{
std::cout<<"B's Construction Done" <<std::endl;
}

void show()
{
A::fun();
//cout << a << endl;
cout<<&A::a << endl; //compilation error
}
};

int main()
{
    B(10,20).show();
    return 0;
}

Now there is a undefined behavior i am able to observe:

If i make my member variable a in class A as public then there will not be any compilation error but output is coming as 1 i dont know why..

class A{
public:
int a
....
....

OUTPUT:

A's construction done

B's Construction Done

A's fun

0027F91C

1 (why 1) and no errors as I was able to get when I tried to access protected member?

Hix answered 18/1, 2013 at 13:53 Comment(1)
@Sergey sorry typing mistakeHix
M
3

Short answer: There is no undefined behavior involved. The behavior you see is:

  • The expression &A::a is an attempt to obtain a pointer to member pointing to member a of class A. If a is protected in A, this expression only passes access checks within class A (or friends of A). In a class B derived from A, you can get the same pointer to member only via the expression &B::a (note that the type of this expression will still be int A::*). So:
    • if A::a is protected in A, the expression &A::a is not allowed in a member function of derived class B. This is your compiler error.
    • if A::a is public in A, this expression is valid, producing a pointer to memeber.
  • Streaming a pointer to member to an ostream, for example using cout << &A::a will print 1. This results from invoking ostream::operator << (bool). You can use the boolalpha manipulator to see that this is indeed the chosen overload: cout << boolalpha << &A::a will print true.
  • If you use the modified expression &(A::a) or simply &a, no pointer to member is formed. Here the address of member a of the current object (i.e the same as &(this->a)) is taken, which is a regular pointer to int. This access to a protected member of a base class subobject of *this is valid, so that variant can be used even if a is protected in A.

Longer explanation:

The standard says (5.3.1/3):

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified- id. If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m. [...]

So the expression &A::a attempts to obtain a pointer-to-member to member a of class A.

In the next paragraph (5.3.1/4), it is elaborated that only the &X::m syntax produces a pointer to member - neither &(X::m), nor &m or plain X::m do:

A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses.

But such an expression is only valid, if access is allowed. In case of a protected member (11.4/1) applies:

An additional access check beyond those described earlier in Clause 11 is applied when a non-static data member or non-static member function is a protected member of its naming class (11.2) As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1), the nested-name-specifier shall denote C or a class derived from C. [...]

In your case access to the protected member a would be granted, because the reference to a occurs in a member of class B, derived from A. As the expression attempts to form a pointer to member, the nested-name-specifier (the part before the final "::a") must denote B. Thus the simplest allowed form is &B::a. The form &A::ais only allowed within members or friends of class A itself.

There is no formatted output operator for pointers to member (neither as istream member nor as free operator function), so the compiler will look at overloads that can be called using a standard conversion (sequence). The only standard conversion from pointers to member to something else is described in 4.12/1:

A prvalue of [...] pointer to member type can be converted to a prvalue of type bool. A [...] null member pointer value is converted to false; any other value is converted to true. [...]

This conversion can be used without additional conversions to call basic_ostream<charT,traits>& basic_ostream<charT,traits>::operator<<(bool n). Other overloads require longer conversion sequences, so that overload is the best match.

As &A::a takes the address of some member, it is not a null member pointer value. Thus it will convert to true, which prints as "1" (noboolalpha) or "true" (boolalpha).

Finally, the expression &(A::a) is valid in a member of B, even if a is protected in A. by the above rules this expression does not form a pointer to member, so the special access rule quoted above does not apply. For such cases 11.4/1 continues:

All other accesses involve a (possibly implicit) object expression (5.2.5). In this case, the class of the object expression shall be C or a class derived from C.

Here the object impression is an implicit (*this), i.e. A::a means the same as (*this).A::a. The type of (*this) obviously is the same as the class where the access occurs (B), so the access is allowed. [Note: int x = A(42).a would not be allowed within B.]

So &(A::a) within B::show() means the same as &(this->a) and that is a plain pointer to int.

Mccomas answered 27/1, 2013 at 0:32 Comment(1)
The standard committee site is at open-std.org/jtc1/sc22/wg21. The standard can be obtained officially from any national ISO member organisation (ANSI in the US) for a fee. But you can get committee working drafts for free. At open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3376.pdf you can find the latest public draft, which is the 2011 standard + some minor, editorial fixes. Beware: that is not light reading!Mccomas
I
4

You are hitting a small quirk in the syntax (not that C++ has few of them...). The default way to access a member variable is through direct use of the name or through this->. That is, the simpler spelling of your show function would be:

void B::show() {
   std::cout << a << std::endl;     // alternatively this->a
   std::cout << &a << std::endl;    //               &(this->a)
}

Which has a simple and consistent syntax. Now the language allows you to drop in extra qualifiers to access members of a base when accessing a member:

std::cout << A::a << std::endl;     // Extra qualification

This is really equivalent to this->A::a and the main uses of the extra qualification are to disambiguate (if two bases had a member a, select the one in A) and in the case of virtual function disabling dynamic dispatch.

Now you can do the same with pointers, as in &this->A::a, which will take the address of the member a in the subobject A of the current object. The problem in this case is that you cannot drop the this-> qualifier, as the syntax &A::a is reserved to obtain a pointer to member, although by adding an extra set of parenthesis, as in &(A::a) the parser can no longer interpret that as obtaining a pointer to member but rather as taking the address of the object represented by A::a, which as seen before is the member in the base.

Iden answered 18/1, 2013 at 15:33 Comment(5)
If you are right then please check the updated question how that work when my class A's member is public?Hix
@Badshah: The answer to that question is not trivial to answer in detail a comment. If the member is protected you cannot access it from the derived type in the same way that void B::f(A&a) { a.a = 5; } fails to compile (protected only grants access to your own A subobject, not any other A object). If you make the member public, then you can obtain the address and &A::a can compile in the context of a member of B and will yield a pointer to member. [...]Palaeozoic
[...] as of the value that you see printed the explanation can be found by determining what operator<< overload is called. The argument is a pointer-to-member, and there is no overload that takes that type, so the compile tries all the existing overloads to determine whether the argument can be converted. From the set of overloads the only one for which there is a conversion is the one that takes a bool, since all pointers can be converted to bool with a value of true if it is non-null or false otherwise. &A::a is not null, and thus gets converted to true and printed as 1 [...]Palaeozoic
[...] This can be verified by modifying the code to request bool to be printed in textual form, rather than as a numeric value: std::cout << std::boolalpha << &A::a << std::endl;. The output of that line of code will be true.Palaeozoic
(BTW, I appreciate the bounty, but you probably need the rep more than I do --having more reputation points will enable some extra features in the interface)Palaeozoic
M
3

Short answer: There is no undefined behavior involved. The behavior you see is:

  • The expression &A::a is an attempt to obtain a pointer to member pointing to member a of class A. If a is protected in A, this expression only passes access checks within class A (or friends of A). In a class B derived from A, you can get the same pointer to member only via the expression &B::a (note that the type of this expression will still be int A::*). So:
    • if A::a is protected in A, the expression &A::a is not allowed in a member function of derived class B. This is your compiler error.
    • if A::a is public in A, this expression is valid, producing a pointer to memeber.
  • Streaming a pointer to member to an ostream, for example using cout << &A::a will print 1. This results from invoking ostream::operator << (bool). You can use the boolalpha manipulator to see that this is indeed the chosen overload: cout << boolalpha << &A::a will print true.
  • If you use the modified expression &(A::a) or simply &a, no pointer to member is formed. Here the address of member a of the current object (i.e the same as &(this->a)) is taken, which is a regular pointer to int. This access to a protected member of a base class subobject of *this is valid, so that variant can be used even if a is protected in A.

Longer explanation:

The standard says (5.3.1/3):

The result of the unary & operator is a pointer to its operand. The operand shall be an lvalue or a qualified- id. If the operand is a qualified-id naming a non-static member m of some class C with type T, the result has type “pointer to member of class C of type T” and is a prvalue designating C::m. [...]

So the expression &A::a attempts to obtain a pointer-to-member to member a of class A.

In the next paragraph (5.3.1/4), it is elaborated that only the &X::m syntax produces a pointer to member - neither &(X::m), nor &m or plain X::m do:

A pointer to member is only formed when an explicit & is used and its operand is a qualified-id not enclosed in parentheses.

But such an expression is only valid, if access is allowed. In case of a protected member (11.4/1) applies:

An additional access check beyond those described earlier in Clause 11 is applied when a non-static data member or non-static member function is a protected member of its naming class (11.2) As described earlier, access to a protected member is granted because the reference occurs in a friend or member of some class C. If the access is to form a pointer to member (5.3.1), the nested-name-specifier shall denote C or a class derived from C. [...]

In your case access to the protected member a would be granted, because the reference to a occurs in a member of class B, derived from A. As the expression attempts to form a pointer to member, the nested-name-specifier (the part before the final "::a") must denote B. Thus the simplest allowed form is &B::a. The form &A::ais only allowed within members or friends of class A itself.

There is no formatted output operator for pointers to member (neither as istream member nor as free operator function), so the compiler will look at overloads that can be called using a standard conversion (sequence). The only standard conversion from pointers to member to something else is described in 4.12/1:

A prvalue of [...] pointer to member type can be converted to a prvalue of type bool. A [...] null member pointer value is converted to false; any other value is converted to true. [...]

This conversion can be used without additional conversions to call basic_ostream<charT,traits>& basic_ostream<charT,traits>::operator<<(bool n). Other overloads require longer conversion sequences, so that overload is the best match.

As &A::a takes the address of some member, it is not a null member pointer value. Thus it will convert to true, which prints as "1" (noboolalpha) or "true" (boolalpha).

Finally, the expression &(A::a) is valid in a member of B, even if a is protected in A. by the above rules this expression does not form a pointer to member, so the special access rule quoted above does not apply. For such cases 11.4/1 continues:

All other accesses involve a (possibly implicit) object expression (5.2.5). In this case, the class of the object expression shall be C or a class derived from C.

Here the object impression is an implicit (*this), i.e. A::a means the same as (*this).A::a. The type of (*this) obviously is the same as the class where the access occurs (B), so the access is allowed. [Note: int x = A(42).a would not be allowed within B.]

So &(A::a) within B::show() means the same as &(this->a) and that is a plain pointer to int.

Mccomas answered 27/1, 2013 at 0:32 Comment(1)
The standard committee site is at open-std.org/jtc1/sc22/wg21. The standard can be obtained officially from any national ISO member organisation (ANSI in the US) for a fee. But you can get committee working drafts for free. At open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3376.pdf you can find the latest public draft, which is the 2011 standard + some minor, editorial fixes. Beware: that is not light reading!Mccomas
J
-5

Its a syntax problem. The & means you ask for the adress of the element immediately on the right.

If you write :

&A::a

It's as if you would have written

(&A)::a

It means youre asking to access to 'a' from '&A', which is not right.

The & doesn't apply only on variables, it can also be used on function, see : http://www.cprogramming.com/tutorial/function-pointers.html

Jephthah answered 18/1, 2013 at 14:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.