3
votes

I am trying to fully understand member access rules defined in multiple paragraphs of the [class.access] section of the C++ standard. They are quite complex or even confusing therefore I need a short but accurate and exhaustive summary.

I compiled this program to test the accessibility of protected members in several situations (since the rules for protected members are the most complex):1

#include <iostream>

class B {
  protected:
    int i = 1;
    static int const I = 1;
};

class X: public B {
  protected:
    int j = 2;
    static int const J = 2;
  public:
    void f();
    friend void g();
};

class D: public X {
  protected:
    int k = 3;
    static int const K = 3;
};

void X::f() {
  B b;
  X x;
  D d;
  //std::cout << b.i;  // error: 'i' is a protected member of 'B'
  std::cout << b.I;
  std::cout << x.i;
  std::cout << x.I;
  std::cout << x.j;
  std::cout << x.J;
  std::cout << d.i;
  std::cout << d.I;
  std::cout << d.j;
  std::cout << d.J;
  //std::cout << d.k;  // error: 'k' is a protected member of 'D'
  //std::cout << d.K;  // error: 'K' is a protected member of 'D'
}

void g() {
  B b;
  X x;
  D d;
  //std::cout << b.i;  // error: 'i' is a protected member of 'B'
  //std::cout << b.I;  // error: 'I' is a protected member of 'B'
  std::cout << x.i;
  std::cout << x.I;
  std::cout << x.j;
  std::cout << x.J;
  std::cout << d.i;
  std::cout << d.I;
  std::cout << d.j;
  std::cout << d.J;
  //std::cout << d.k;  // error: 'k' is a protected member of 'D'
  //std::cout << d.K;  // error: 'K' is a protected member of 'D'
}

int main() {
  B b;
  X x;
  D d;
  //std::cout << b.i;  // error: 'i' is a protected member of 'B'
  //std::cout << b.I;  // error: 'I' is a protected member of 'B'
  //std::cout << x.i;  // error: 'i' is a protected member of 'B'
  //std::cout << x.I;  // error: 'I' is a protected member of 'B'
  //std::cout << x.j;  // error: 'j' is a protected member of 'X'
  //std::cout << x.J;  // error: 'J' is a protected member of 'X'
  //std::cout << d.i;  // error: 'i' is a protected member of 'B'
  //std::cout << d.I;  // error: 'I' is a protected member of 'B'
  //std::cout << d.j;  // error: 'j' is a protected member of 'X'
  //std::cout << d.J;  // error: 'J' is a protected member of 'X'
  //std::cout << d.k;  // error: 'k' is a protected member of 'D'
  //std::cout << d.K;  // error: 'K' is a protected member of 'D'
  return 0;
}

I came to this conclusion on direct accessibility:2

  • public members of a class are directly accessible to any entities (cf. [class.access/base-5.1]);
  • private members of a class are directly accessible only to members and friends of that class (cf. [class.access/base-5.2]);
  • protected members of a class are directly accessible only to members and friends of that class (cf. [class.access/base-5.3]), to members and friends of the base classes of that class if those protected members are inherited from those base classes or the base classes of those base classes (cf. [class.access/base-5.4]),3 and to members of the derived classes of that class (cf. [class.access/base-5.3]) if those protected members are not non-static data members nor non-static member functions (cf. [class.access/class.protected-1]).

Is my summary correct?


1 I used the Clang 9.0.0 compiler with C++ 17.

2 Access to a member i of a class B can be either direct, that is through that class: b.i (direct access), or indirect, that is through a derived class D of that class: d.i (inheritance access). Since members inherited by a derived class are members of that derived class with changes in their accessibility (cf. [class.access/base-1]), inheritance access to a member of a class can be treated as direct access to the inherited member of the derived class of that class. In other words, only direct access needs to be considered.

3 My clause here differs slightly from the referenced clause of the standard [class.access/base-5.4]:

A member m is accessible at the point R when named in class N if

  • there exists a base class B of N that is accessible at R, and m is accessible at R when named in class B.

This is intended as the compiler behaves differently and my feeling is that the compiler is right. In my opinion there are two problems with the clause of the standard:

  • the access point R should be restricted to members and friends of class B (which is what the compiler does by raising errors for the d.* accesses in main in the program);
  • the member m in class N should be restricted to be inherited from class B, not overridden by class N (which is what the compiler would have done by raising errors for the d.i, d.I, d.j, and d.J accesses in X::f and g, had i, I, j and J been overridden in D in the program).
2
What do you mean by and to members of base classes of that class if the protected members are static. A base class gets no special access to things that derive from it.NathanOliver
Here you can find good summary of access qualifiers stackoverflow.com/questions/860339/…Tarek Dakhran
@NathanOliver Oops, I inverted everything. Updated, thanks.Maggyero
@TarekDakhran My post is not about the inheritance of access specifiers, which is rather straightforward. It is about the semantics of access specifiers.Maggyero
The standard is intended to be "accurate and exhaustive". True, it's not exactly short, but that's a direct result of trying to be both accurate and exhaustive. Your summary misses the effect of private and protected inheritance, for instance.MSalters

2 Answers

0
votes

If your question is based on access, these are the rules within c++. I will do a basic summary below, but for the exhaustive explanation please go here. This will go into greater detail on how each works.

public
A public member of a class is accessible anywhere

protected
1. to the members and friends of that class
2. to the members and friends (until C++17) of any derived class of that class, but only when the class of the object through which the protected member is accessed is that derived class or a derived class of that derived class

private
A private member of a class is only accessible to the members and friends of that class, regardless of whether the members are on the same or different instances

To see examples please go to the link above.

With nested classes you are within scope of that the base class, so private and protected members are able to accessed. If the member is static, you will be able access directly, otherwise the object of that class will have to be constructed in order to access those members within that class. Here is an example from class X above:

class X: public B {
public:
    class A {
    public:
        void b() {
            std::cout << J << std::endl;
            std::cout << S << std::endl;
        }
        void d(X x) {
            std::cout << x.j << std::endl;
            std::cout << x.s << std::endl;
        }
    };

    void f();

protected:
    int j = 2;
    static int const J = 2;
private:
    friend void g();
    int s = 3;
    static int const S = 4;
};

Here is what is how public, protected, and private is meant when using them for inheritance

public
When a class uses public member access specifier to derive from a base, all public members of the base class are accessible as public members of the derived class and all protected members of the base class are accessible as protected members of the derived class (private members of the base are never accessible unless friended)

protected
When a class uses protected member access specifier to derive from a base, all public and protected members of the base class are accessible as protected members of the derived class (private members of the base are never accessible unless friended)
private
When a class uses private member access specifier to derive from a base, all public and protected members of the base class are accessible as private members of the derived class (private members of the base are never accessible unless friended).

Note: Derived classes inherits all methods of the base class with the following exception.

  • Constructors, destructors and copy constructors of the base class

  • Overloaded operators such of the base class -- These may not act as you would expect, and should be implemented in a way that you override for each operator for each class.

  • The friend functions of the base class.

Now as for the friend specifier, this is from the cpp reference here Here you will have examples and detailed explanations on how to use it.

You will also find examples for a lot of information on there when it comes to the standard library, also you will be able to see what is coming to the standard in the future and what features your compiler supports.

-1
votes

You mist a not so intuitive access, c++ is compile dependent and one of the effects is that all instances of are the "same" in memory and can access each other private variable if a function on that class can accept references of "himself", for more details check about the class variable this.

#include <iostream>

class MyClass{

    int num = 69;

    public:

        void set_num(int n){
            num = n;
        };
        int get_num() {
            return num;
        }

        int get_num_from(MyClass * ptr_ref) {
            return ptr_ref->num;
        }
        int get_num_from(MyClass ref) {
            return ref.num;
        }

};


int main(){

    MyClass class_a;
    MyClass class_b;

    class_a.set_num(0);

    std::cout << "class_a -> " << class_a.get_num() << std::endl;
    std::cout << "class_b ref -> " << class_b.get_num_from(class_a) << std::endl;
    std::cout << "class_b ptr_ref -> " << class_b.get_num_from(&class_a) << std::endl;

}

Output:

class_a -> 0
class_b ref -> 0
class_b ptr_ref -> 0