subtle C++ inheritance error with protected fields

33,029

Solution 1

Since B is publicly inherited from A, A's protected member(s) become B's protected member(s), so B can access its protected members as usual from its member function(s). That is, the objects of B can access the protected members of B from its member functions.

But A's protected members cannot be accessed outside the class, using object of type A.

Here is the relevant text from the Standard (2003)

11.5 Protected member access [class.protected]

When a friend or a member function of a derived class references a protected nonstatic member function or protected nonstatic data member of a base class, an access check applies in addition to those described earlier in clause 11.102) Except when forming a pointer to member (5.3.1), the access must be through a pointer to, reference to, or object of the derived class itself (or any class derived from that class) (5.2.5). If the access is to form a pointer to member, the nested-name-specifier shall name the derived class (or any class derived from that class).

And the example follows from the Standard (2003) itself as:

[Example:

class B {
  protected:
  int i;
  static int j;
};

class D1 : public B {
};

class D2 : public B {
  friend void fr(B*,D1*,D2*);
  void mem(B*,D1*);
};

void fr(B* pb, D1* p1, D2* p2)
{
  pb->i = 1; // ill-formed
  p1->i = 2; // ill-formed
  p2->i = 3; // OK (access through a D2)
  p2->B::i = 4; // OK (access through a D2, even though naming class is B)
  int B::* pmi_B = &B::i; // ill-formed
  int B::* pmi_B2 = &D2::i; // OK (type of &D2::i is int B::*)
  B::j = 5; // OK (because refers to static member)
  D2::j =6; // OK (because refers to static member)
}
void D2::mem(B* pb, D1* p1)
{
  pb->i = 1; // ill-formed
  p1->i = 2; // ill-formed
  i = 3; // OK (access through this)
  B::i = 4; // OK (access through this, qualification ignored)
  int B::* pmi_B = &B::i; // ill-formed
  int B::* pmi_B2 = &D2::i; // OK
  j = 5; // OK (because j refers to static member)
  B::j = 6; // OK (because B::j refers to static member)
}
void g(B* pb, D1* p1, D2* p2)
{
  pb->i = 1; // ill-formed
  p1->i = 2; // ill-formed
  p2->i = 3; // ill-formed
}
—end example]

Note in the above example fr() is a friend function of D2, mem() is a member function of D2, and g() is neither a friend, nor a member function.

Solution 2

Consider:

class A {
protected:
  int x;
};

class C : public A
{
};

class B : public A {
protected:
  unique_ptr<A> a;
public:
  B() : a(new C) // a now points to an instance of "C"
  { }

  void foo() {
    int w = a->x;  // B accessing a protected member of a C? Oops.
  }
};

Solution 3

In Public Inheritance:
All Public members of the Base Class become Public Members of the derived class &
All Protected members of the Base Class become Protected Members of the Derived Class.

As per the above rule:
protected member x from A becomes protected member of class B.

class B can access its own protected members in its member function foo but it can only access members of A through which it was derived not all A classes.

In this case, class B contains a A pointer a, It cannot access the protected members of this contained class.

Why can the B::foo() access the members of the contained class B pointer b?

The rule is:
In C++ access control works on per-class basis, not on per-object basis.
So an instance of class B will always have access to all the members of another instance of class B.

An Code Sample, which demonstrates the rule:

#include<iostream>

class MyClass 
{
    public: 
       MyClass (const std::string& data) : mData(data) 
       {
       }

       const std::string& getData(const MyClass &instance) const 
       {
          return instance.mData;
       }

    private:
      std::string mData;
};

int main() {
  MyClass a("Stack");
  MyClass b("Overflow");

  std::cout << "b via a = " << a.getData(b) << std::endl;
  return 0;
}

Solution 4

Why can B::foo() access b's x field, but not a's x field?

A protected member can only be accessed by other members of the same class (or derived classes).

b->x points to a protected member of an instance of class B (through inheritance), so B::foo() can access it.

a->x points to a protected member of an instance of class A, so B::foo() cannot access it.

Share:
33,029
wcochran
Author by

wcochran

Computer Scientist for Quintar Inc., Previously Software Engineer at Intel Sports, Previously Associate Professor at Washington State U.

Updated on July 09, 2022

Comments

  • wcochran
    wcochran almost 2 years

    Below is a subtle example of accessing an instance's protected field x. B is a subclass of A so any variable of type B is also of type A. Why can B::foo() access b's x field, but not a's x field?

    class A {
    protected:
      int x;
    };
    
    class B : public A {
    protected:
      A *a;
      B *b;
    public:
      void foo() {
        int u = x;     // OK : accessing inherited protected field x
        int v = b->x;  // OK : accessing b's protected field x
        int w = a->x;  // ERROR : accessing a's protected field x
      }
    };
    

    Here is the error I get with g++

    $ g++ -c A.cpp
    A.cpp: In member function ‘void B::foo()’:
    A.cpp:3: error: ‘int A::x’ is protected
    A.cpp:14: error: within this context
    
  • Billy ONeal
    Billy ONeal almost 13 years
    Because it's public inheritance, A's protected members become B's protected members. Not private.
  • Nawaz
    Nawaz almost 13 years
    @Billy: Correct. And Corrected the answer as well.
  • Alok Save
    Alok Save almost 13 years
    In Public Ihneritance, The protected members of Base class become protected memebers of derived class & public members of base class become public members of derived class.
  • Billy ONeal
    Billy ONeal almost 13 years
    But x is a protected member of B in this example, and access is not allowed. I don't think this answers the question.
  • Alok Save
    Alok Save almost 13 years
    @Billy ONeal: In C++ access control works on per-class basis, not on per-object basis, So a object of class B can access members of another object of class B.
  • wcochran
    wcochran almost 13 years
    That doesn't explain it, because you would have the same argument if both a and foo were members of A (i.e., a could be an instance of C) -- but this compiles just fine with C++!
  • wcochran
    wcochran almost 13 years
    But I am accessing "through a pointer to, reference to, or object of the derived class itself (or any class derived from that class)".
  • Billy ONeal
    Billy ONeal almost 13 years
    @wcochran: Not true. In this example, B is not in C's inheritance chain at all -- it shouldn't have access to any part of C. If both were members of A, then A would be in the inheritance chain of both classes.
  • wcochran
    wcochran almost 13 years
    If I added a the field A *a; to A (forget 'B' for the moment), then a could be a ptr to an instance of type C -- same problem, compiles fine.
  • Billy ONeal
    Billy ONeal almost 13 years
    @wchchran: No, it does not. ideone.com/29pyK If that's not what you mean, post an example, and I'll explain.
  • Billy ONeal
    Billy ONeal almost 13 years
    @wcochran: The derived class itself is B, not A. You're accessing it through A. (Or rather, a pointer to A)
  • wcochran
    wcochran almost 13 years
    here's an example. Note how a could be an instance of C.
  • Tamas Demjen
    Tamas Demjen almost 13 years
    @wcochran That's different, C is an A. The problem is when unrelated classes like B and C try to access each other's protected members.
  • Billy ONeal
    Billy ONeal almost 13 years
    @wcochran: Not true. From A's point of view, it's still just an A. A is in C's inheritance chain. B is not in C's inheritance chain in my example, however.
  • David Rodríguez - dribeas
    David Rodríguez - dribeas almost 13 years
    Since B is publicly inherited from A, A's protected member(s) become B's protected member(s) is still misleading in at least two ways. The inheritance does not need to be public, protected suffices to make the attribute protected by default. The second place where this might cause misunderstanding is that inheritance makes it protected. The fact that it is protected means that the derived class will have access to it, and in doing so, it has the right to convert the field into public or private. Consider struct derived : base { using base::x; } that will make x public.
  • wcochran
    wcochran almost 13 years
    Yes, but x is inherited by both B and C. The general OOP rule is that B is-a A and B can do anything A can do. That's the whole point of my post.
  • Billy ONeal
    Billy ONeal almost 13 years
    @wcochran: No, that's not the general OOP rule. The general OOP rule is that derived classes inherit the behavior of their base classes. Allowing that kind of access would be inheriting the behavior of C, even though C is not in B's inheritance hierarchy (and therefore is ill formed)
  • Billy ONeal
    Billy ONeal almost 13 years
    @David: In such cases, the derived class is making that call explicitly. I don't see how protected inheritance has anything to do with what we're talking about. Nawaz is talking solely about what happens when public inheritance is in play. (Besides, I don't know about you; I've never seen protected inheritance outside of a textbook)
  • Gabriel Staples
    Gabriel Staples almost 4 years
    You mistakenly said "Since B is publicly inherited from A", but you should have said inheriting, not inherited. This makes your direction in the wording backwards.