Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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?

like image 526
Astro - Amit Avatar asked Jan 18 '13 13:01

Astro - Amit


People also ask

How is protected access specifier different from other access specifiers while further inheriting a class?

The protected access specifier allows the class the member belongs to, friends, and derived classes to access the member. However, protected members are not accessible from outside the class.

Can derived classes access private members?

Private members of the base class cannot be used by the derived class unless friend declarations within the base class explicitly grant access to them.

What is an inheritance list out the different types of access specifier?

In C++, there are three access specifiers: public - members are accessible from outside the class. private - members cannot be accessed (or viewed) from outside the class. protected - members cannot be accessed from outside the class, however, they can be accessed in inherited classes.

What is the role of protected access specifier?

Remarks. The protected keyword specifies access to class members in the member-list up to the next access specifier ( public or private ) or the end of the class definition. Class members declared as protected can be used only by the following: Member functions of the class that originally declared these members.


2 Answers

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.

like image 155
David Rodríguez - dribeas Avatar answered Nov 08 '22 19:11

David Rodríguez - dribeas


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.

like image 28
JoergB Avatar answered Nov 08 '22 20:11

JoergB