Search code examples
c++inheritanceclass-visibility

c++ mastering inheritance with public, protected and private


This is what I understand and I encourage you to correct me please: the visibility rules of inheritance of c++ classes are important if classes are inherited multiple times, correct?

class Base {
public :
    int a;
protected : 
    int b;
private :
    int c;
};

class Layer_1 : private Base {  /* everything from ´Base´ is now */
    int z{Base::a};             /* considered a private of ´Layer_1´ */
    int r{Base::b};             /* but accessible here */
};

class Layer_2 : Layer_1 {   /* Nothing in ´Layer_1´ is accessible from */
    int y{Layer_1::a};      /* ´Layer_2´,as ´Layer_1´ inherited everything */
};  // invalid ~~~~^        /* from ´Base´ into their private scope */
  1. Do they have any other purpose?
  2. This has to be used carefully as once I inherit a class as protected I can't go public again, etc. Correct?
  3. If I only have a Base and Layer class, it doesn't really matter how I inherit from Base as I'm not inheriting from Layer, correct?
  4. Are there any known conventions or industry standards how and when to use public/protected/private or has this to be decided on each individual occasion and there's no 'rule'?

I hope this is no duplicate. I've looked but there was no question that answered this specific portion for me.

I got a bit confused and stumped when I couldn't really identify a pattern to how to use the different keywords.


Solution

  • The most important thing to know about access control in C++ is that it applies coequally and orthogonally to two different kinds of things:

    • To the class's members (data members, member functions, member typedefs, etc.), i.e., its HAS-A relationships; and,
    • To the class's base-class relationships, i.e. its IS-A relationships.

    For example:

    class Dolphin : public Animal {
    public:
      int dorsal_fin_;
    };
    

    A Dolphin HAS-A dorsal fin, and a Dolphin IS-AN Animal; both of these facts are public knowledge, available to anyone in the program. This means any random stranger in the program can write

    Dolphin *d = ~~~;
    use(d->dorsal_fin_);
    Animal *a = d;  // implicit conversion to pointer-to-base
    

    and it'll Just Work. Also, that stranger can access d as if it were an Animal (because it is publicly known to be an Animal) — he can use Animal's public API on d. (Or even, I suppose, use Animal's private API, if Animal is friendly enough toward him.)

    Contrary example:

    class Dolphin : private Mammal {
    private:
      int vestigial_leg_;
    };
    

    A Dolphin HAS-A vestigial leg, and a Dolphin IS-A Mammal; but both of these facts are private knowledge, accessible only to class Dolphin itself (and its members and its friends). That means that within Dolphin I can write:

    use(d->vestigial_leg_);
    Mammal *m = d;  // implicit conversion to pointer-to-base
    

    but a random stranger could not write those lines, because from his point of view he doesn't know that a Dolphin HAS-A vestigial leg and he doesn't know that a Dolphin IS-A Mammal. Those private relationships are inaccessible to him: he's not allowed to exploit them in the code he writes.

    IMO everything else falls naturally out of the above, as logical consequences of these two rules.


    Finally, there's protected, which means "This relationship is accessible to me (and my members and my friends) and also to my child classes," except that it has some additional fiddly bits around the edges: basically the relationship is accessible to me but only if I'm operating on this instead of on some random pointer I got from someone else. It's very fiddly and in my experience doesn't come up enough to bother with the exact details.


    I strongly recommend that you use only a single level of inheritance. Use inheritance for classical polymorphism, in which you have an abstract base class to define the interface and a concrete derived class to define the implementation and then that's it. Don't use "implementation inheritance" if you can at all help it.

    I fairly strongly recommend that you never use protected; if you think you need to touch something in a child class, just promote it to public. That is, factor your API carefully so that you can make each API member either 100% public or 100% private. This will complete the virtuous cycle: you'll never have to learn the subtleties of protected because you'll never run into those subtleties because you'll never use it.