What is the difference between public, private, and protected inheritance in C++?
In C++, inheritance allows you to create new classes (derived classes) from existing ones (base classes), promoting code reuse and a more organized object-oriented design. However, the access specifiers for inheritance—public, protected, and private—significantly affect how members of the base class are inherited and accessed in the derived class. Understanding these differences is crucial for designing robust and maintainable hierarchies.
1. Public Inheritance
When you use public inheritance, all public members of the base class become public in the derived class, and all protected members of the base class remain protected in the derived class. Private members of the base class are still inaccessible directly by the derived class (unless there are friend relationships or other special circumstances).
class Base { public: int publicVar; protected: int protectedVar; private: int privateVar; }; // Derived publicly inherits Base class DerivedPublic : public Base { public: void accessMembers() { publicVar = 1; // Allowed protectedVar = 2; // Allowed // privateVar = 3; // Not accessible } };
When to use Public Inheritance:
- You’re modeling an “is-a” relationship (e.g.,
Dog
is aAnimal
). - You expect users of your derived class to interact with the public interface of the base class.
- You want a straightforward, polymorphic interface where the derived class can be substituted wherever the base class is expected.
2. Protected Inheritance
With protected inheritance, the public and protected members of the base class become protected in the derived class. This means that external code cannot directly access what were public members of the base class through the derived class, but derived classes still have access to them internally.
// Derived inherits Base as protected class DerivedProtected : protected Base { public: void accessMembers() { publicVar = 1; // Accessible, but now treated as protected protectedVar = 2; // Accessible } }; int main() { DerivedProtected dp; // dp.publicVar = 10; // Error: it's now protected return 0; }
When to use Protected Inheritance:
- You want to reuse the functionality of the base class internally within the derived class, but you don’t want that functionality to be exposed publicly.
- It’s a niche design choice. Typically, public or private inheritance is used more frequently.
3. Private Inheritance
With private inheritance, all public and protected members of the base class become private within the derived class. Only the derived class itself (and friends) can access them. They are inaccessible to further derived classes and to the outside world.
// Derived inherits Base as private class DerivedPrivate : private Base { public: void accessMembers() { publicVar = 1; // Accessible internally but private within DerivedPrivate protectedVar = 2; // Also accessible internally } }; int main() { DerivedPrivate dpr; // dpr.publicVar = 10; // Error: now private within DerivedPrivate return 0; }
When to use Private Inheritance:
- You’re using the base class implementation rather than modeling an “is-a” relationship.
- The derived class only needs some or all of the base class’s functionality internally, and you have no intention of exposing or overriding that interface publicly.
- Conceptually, private inheritance can be seen as “implemented in terms of” rather than “is-a.”
Summary of Behaviors
-
Public Inheritance
- Public members stay public; protected members stay protected.
- Derivable and substitutable: a
DerivedPublic
object can be used anywhere aBase
is expected (polymorphic usage).
-
Protected Inheritance
- Public and protected members in the base become protected in the derived class.
- Rarely used in practice; effectively limits how the derived class is exposed to external code.
-
Private Inheritance
- Public and protected members in the base become private.
- Not an “is-a” relationship; more like “has/uses features of base internally.”
- No external substitution of derived for base is possible (no polymorphic usage from the outside).
Practical Considerations
-
Polymorphism
Public inheritance is typically necessary if you want to use polymorphism (e.g., virtual functions). If you need to pass a derived class object where a base pointer is expected, you usually rely on public inheritance. -
Encapsulation
If you only need the base class’s functionality internally, private inheritance can be a way to hide it from external access. -
Composition Over Inheritance
In modern design principles, if your relationship is purely about using the base class’s features (and not truly an “is-a” relationship), it’s often better to use composition (a data member of the base type) rather than private inheritance. This is more explicit and typically easier to understand and maintain. -
Code Readability
Public inheritance is the most common and communicates your intent clearly. If you’re using protected or private inheritance, consider if composition might be more suitable or more transparent.
Why This Matters in Coding Interviews
Interviewers frequently check your understanding of C++ inheritance to gauge:
- How well you grasp object-oriented design principles.
- Whether you know when to model an “is-a” relationship vs. “has-a” relationship.
- Your familiarity with access specifiers, which can affect both design and maintainability.
Demonstrating clarity on public, protected, and private inheritance—and when to apply them—reflects advanced knowledge of C++ and OOP best practices.
Further Learning Resources
If you’re looking to excel in coding interviews or deepen your C++ understanding, consider these courses from DesignGurus.io:
-
Grokking the Coding Interview: Patterns for Coding Questions
Master the common coding patterns tested at top tech companies. -
Grokking System Design Fundamentals
If you’re moving into system design topics, this is an excellent place to start.
You can also check out the DesignGurus.io YouTube channel for free tutorials and insights, or consider a Coding Mock Interview to get personalized feedback from ex-FAANG engineers.
Key Takeaway
Choose your inheritance type based on the relationship you want to model. Public inheritance for an “is-a” relationship and polymorphism, private inheritance for “uses base class internally,” and protected inheritance rarely unless you specifically need to hide inherited public members from outside while retaining them for derived classes.