Ready to practice?
Sign up to access interactive coding exercises and track your progress.
Controlling Inherited Member Access
Learn inheritance and access specifiers to create hierarchical relationships between classes.
Inheritance and Access Specifiers
In the previous lessons in this chapter, you've learned how base inheritance functions. In all our examples so far, we've used public inheritance. That is, our derived class publicly inherits the base class.
In this lesson, we'll examine public inheritance more closely, as well as the two other types of inheritance (private and protected). We'll also explore how different types of inheritance interact with access specifiers to allow or restrict access to members.
To this point, you've seen the private and public access specifiers, which determine who can access the members of a class. As a quick refresher, public members can be accessed by anybody. Private members can only be accessed by member functions of the same class or friends. This means derived classes cannot access private members of the base class directly!
class Storage
{
private:
int m_privateData {}; // can only be accessed by Storage members and friends (not derived classes)
public:
int m_publicData {}; // can be accessed by anybody
};
This is straightforward, and you should be comfortable with it by now.
The protected access specifier
When dealing with inherited classes, things become a bit more complex.
C++ has a third access specifier that we haven't discussed yet because it's only useful in an inheritance context. 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.
class Storage
{
public:
int m_publicData {}; // can be accessed by anybody
protected:
int m_protectedData {}; // can be accessed by Storage members, friends, and derived classes
private:
int m_privateData {}; // can only be accessed by Storage members and friends (but not derived classes)
};
class Container: public Storage
{
public:
Container()
{
m_publicData = 1; // allowed: can access public base members from derived class
m_protectedData = 2; // allowed: can access protected base members from derived class
m_privateData = 3; // not allowed: cannot access private base members from derived class
}
};
int main()
{
Storage storage;
storage.m_publicData = 1; // allowed: can access public members from outside class
storage.m_protectedData = 2; // not allowed: cannot access protected members from outside class
storage.m_privateData = 3; // not allowed: cannot access private members from outside class
return 0;
}
In the above example, you can see that the protected base member m_protectedData is directly accessible by the derived class, but not by the public.
So when should I use the protected access specifier?
With a protected attribute in a base class, derived classes can access that member directly. This means that if you later change anything about that protected attribute (the type, what the value represents, etc.), you'll probably need to change both the base class AND all of the derived classes.
Therefore, using the protected access specifier is most useful when you (or your team) are going to be the ones deriving from your own classes, and the number of derived classes is reasonable. That way, if you make a change to the implementation of the base class, and updates to the derived classes are necessary as a result, you can make the updates yourself (and it won't take forever, since the number of derived classes is limited).
Making your members private means the public and derived classes can't directly make changes to the base class. This is beneficial for insulating the public or derived classes from implementation changes, and for ensuring invariants are maintained properly. However, it also means your class may need a larger public (or protected) interface to support all of the functions that the public or derived classes need for operation, which has its own cost to build, test, and maintain.
In general, it's better to make your members private if you can, and only use protected when derived classes are planned and the cost to build and maintain an interface to those private members is too high.
Favor private members over protected members.
First, there are three different ways for classes to inherit from other classes: public, protected, and private.
To do so, simply specify which type of access you want when choosing the class to inherit from:
// Inherit from Storage publicly
class PublicContainer: public Storage
{
};
// Inherit from Storage protectedly
class ProtectedContainer: protected Storage
{
};
// Inherit from Storage privately
class PrivateContainer: private Storage
{
};
class DefaultContainer: Storage // Defaults to private inheritance
{
};
If you do not choose an inheritance type, C++ defaults to private inheritance (just like members default to private access if you do not specify otherwise).
That gives us 9 combinations: 3 member access specifiers (public, private, and protected), and 3 inheritance types (public, private, and protected).
So what's the difference between these? In a nutshell, when members are inherited, the access specifier for an inherited member may be changed (in the derived class only) depending on the type of inheritance used. Put another way, members that were public or protected in the base class may change access specifiers in the derived class.
This might seem confusing, but it's not that complicated. We'll spend the rest of this lesson exploring this in detail.
Keep in mind the following rules as we step through the examples:
- A class can always access its own (non-inherited) members.
- The public accesses the members of a class based on the access specifiers of the class it is accessing.
- A derived class accesses inherited members based on the access specifier inherited from the parent class. This varies depending on the access specifier and type of inheritance used.
Public inheritance
Public inheritance is by far the most commonly used type of inheritance. In fact, you'll rarely see or use the other types of inheritance, so your primary focus should be on understanding this section. Fortunately, public inheritance is also the easiest to understand. When you inherit a base class publicly, inherited public members stay public, and inherited protected members stay protected. Inherited private members, which were inaccessible because they were private in the base class, stay inaccessible.
Access specifier in base class
Access specifier when inherited publicly
Public
Public
Protected
Protected
Private
Inaccessible
Here's an example showing how things work:
class Storage
{
public:
int m_publicData {};
protected:
int m_protectedData {};
private:
int m_privateData {};
};
class PublicContainer: public Storage // note: public inheritance
{
// Public inheritance means:
// Public inherited members stay public (so m_publicData is treated as public)
// Protected inherited members stay protected (so m_protectedData is treated as protected)
// Private inherited members stay inaccessible (so m_privateData is inaccessible)
public:
PublicContainer()
{
m_publicData = 1; // okay: m_publicData was inherited as public
m_protectedData = 2; // okay: m_protectedData was inherited as protected
m_privateData = 3; // not okay: m_privateData is inaccessible from derived class
}
};
int main()
{
// Outside access uses the access specifiers of the class being accessed.
Storage storage;
storage.m_publicData = 1; // okay: m_publicData is public in Storage
storage.m_protectedData = 2; // not okay: m_protectedData is protected in Storage
storage.m_privateData = 3; // not okay: m_privateData is private in Storage
PublicContainer container;
container.m_publicData = 1; // okay: m_publicData is public in PublicContainer
container.m_protectedData = 2; // not okay: m_protectedData is protected in PublicContainer
container.m_privateData = 3; // not okay: m_privateData is inaccessible in PublicContainer
return 0;
}
This is the same as the example above where we introduced the protected access specifier, except that we've instantiated the derived class as well, just to show that with public inheritance, things work identically in the base and derived class.
Public inheritance is what you should be using unless you have a specific reason not to.
Use public inheritance unless you have a specific reason to do otherwise.
Protected inheritance is the least common method of inheritance. It is almost never used, except in very particular cases. With protected inheritance, the public and protected members become protected, and private members stay inaccessible.
Because this form of inheritance is so rare, we'll skip the example and just summarize with a table:
Access specifier in base class
Access specifier when inherited protectedly
Public
Protected
Protected
Protected
Private
Inaccessible
Private inheritance
With private inheritance, all members from the base class are inherited as private. This means private members are inaccessible, and protected and public members become private.
Note that this does not affect the way that the derived class accesses members inherited from its parent! It only affects the code trying to access those members through the derived class.
class Storage
{
public:
int m_publicData {};
protected:
int m_protectedData {};
private:
int m_privateData {};
};
class PrivateContainer: private Storage // note: private inheritance
{
// Private inheritance means:
// Public inherited members become private (so m_publicData is treated as private)
// Protected inherited members become private (so m_protectedData is treated as private)
// Private inherited members stay inaccessible (so m_privateData is inaccessible)
public:
PrivateContainer()
{
m_publicData = 1; // okay: m_publicData is now private in PrivateContainer
m_protectedData = 2; // okay: m_protectedData is now private in PrivateContainer
m_privateData = 3; // not okay: derived classes can't access private members in the base class
}
};
int main()
{
// Outside access uses the access specifiers of the class being accessed.
// In this case, the access specifiers of storage.
Storage storage;
storage.m_publicData = 1; // okay: m_publicData is public in Storage
storage.m_protectedData = 2; // not okay: m_protectedData is protected in Storage
storage.m_privateData = 3; // not okay: m_privateData is private in Storage
PrivateContainer container;
container.m_publicData = 1; // not okay: m_publicData is now private in PrivateContainer
container.m_protectedData = 2; // not okay: m_protectedData is now private in PrivateContainer
container.m_privateData = 3; // not okay: m_privateData is inaccessible in PrivateContainer
return 0;
}
To summarize in table form:
Access specifier in base class
Access specifier when inherited privately
Public
Private
Protected
Private
Private
Inaccessible
Private inheritance can be useful when the derived class has no obvious relationship to the base class, but uses the base class for implementation internally. In such a case, we probably don't want the public interface of the base class to be exposed through objects of the derived class (as it would be if we inherited publicly).
In practice, private inheritance is rarely used.
A final example
class Storage
{
public:
int m_publicData {};
protected:
int m_protectedData {};
private:
int m_privateData {};
};
Storage can access its own members without restriction. The public can only access m_publicData. Derived classes can access m_publicData and m_protectedData.
class SecondLevel : private Storage // note: private inheritance
{
// Private inheritance means:
// Public inherited members become private
// Protected inherited members become private
// Private inherited members stay inaccessible
public:
int m_publicData2 {};
protected:
int m_protectedData2 {};
private:
int m_privateData2 {};
};
SecondLevel can access its own members without restriction. SecondLevel can access Storage's m_publicData and m_protectedData members, but not m_privateData. Because SecondLevel inherited Storage privately, m_publicData and m_protectedData are now considered private when accessed through SecondLevel. This means the public cannot access these variables when using a SecondLevel object, nor can any classes derived from SecondLevel.
class ThirdLevel : public SecondLevel
{
// Public inheritance means:
// Public inherited members stay public
// Protected inherited members stay protected
// Private inherited members stay inaccessible
public:
int m_publicData3 {};
protected:
int m_protectedData3 {};
private:
int m_privateData3 {};
};
ThirdLevel can access its own members without restriction. ThirdLevel can access SecondLevel's m_publicData2 and m_protectedData2 members, but not m_privateData2. Because ThirdLevel inherited SecondLevel publicly, m_publicData2 and m_protectedData2 keep their access specifiers when accessed through ThirdLevel. ThirdLevel has no access to Storage's m_privateData, which was already private in Storage. Nor does it have access to Storage's m_protectedData or m_publicData, both of which became private when SecondLevel inherited them.
Summary
Protected access specifier: The protected access specifier allows the class owning the member, friends, and derived classes to access that member, but prevents access from outside the class. This provides a middle ground between public (accessible to everyone) and private (accessible only to the class and friends), specifically designed for inheritance scenarios.
When to use protected: Use protected members when you or your team will be deriving from your own classes and the number of derived classes is reasonable. This allows direct access from derived classes while maintaining encapsulation from the public. However, favor private members when possible, only using protected when the cost of building and maintaining a larger interface is too high.
Three types of inheritance: Classes can inherit publicly, protectedly, or privately using the syntax class Derived : public/protected/private Base. Public inheritance (the most common) keeps public members public and protected members protected. Protected inheritance makes both public and protected members become protected in the derived class. Private inheritance makes both public and protected members become private in the derived class.
Public inheritance (most common) maintains the access levels of inherited members: public remains public, protected remains protected, and private remains inaccessible. This is the standard form of inheritance and should be used unless there's a specific reason to do otherwise.
Private and protected inheritance: Private inheritance makes all inherited members private in the derived class, useful when the derived class has no obvious relationship to the base but uses it for internal implementation. Protected inheritance (rare) makes public and protected members become protected. Both affect how code accesses members through the derived class, not how the derived class itself accesses inherited members.
Access rules: A class can always access its own non-inherited members without restriction. The public accesses class members based on the access specifiers of that class. Derived classes access inherited members based on the access specifier inherited from the parent class, which varies depending on the access specifier and inheritance type used.
The way that the access specifiers, inheritance types, and derived classes interact causes significant confusion. To try and clarify things as much as possible:
First, a class (and friends) can always access its own non-inherited members. The access specifiers only affect whether outsiders and derived classes can access those members.
Second, when derived classes inherit members, those members may change access specifiers in the derived class. This does not affect the derived classes' own (non-inherited) members (which have their own access specifiers). It only affects whether outsiders and classes derived from the derived class can access those inherited members.
Here's a table of all of the access specifier and inheritance types combinations:
Access specifier in base class
Access specifier when inherited publicly
Access specifier when inherited privately
Access specifier when inherited protectedly
Public
Public
Private
Protected
Protected
Protected
Private
Protected
Private
Inaccessible
Inaccessible
Inaccessible
As a final note, although in the examples above, we've only shown examples using member variables, these access rules hold true for all members (e.g., member functions and types declared inside the class).
Controlling Inherited Member Access - Quiz
Test your understanding of the lesson.
Practice Exercises
Access Specifier Exploration
Create a BankAccount base class with public balance getter, protected deposit method, and private account number. Create a SavingsAccount derived class that uses the protected method to add interest.
Lesson Discussion
Share your thoughts and questions
No comments yet. Be the first to share your thoughts!