Question
Public vs Private vs Protected Inheritance in C++
Question
In C++, what is the difference between public, private, and protected inheritance, and how do they affect access to members of a base class from a derived class and from outside the class hierarchy?
Short Answer
By the end of this page, you will understand how inheritance access modes work in C++, how they change the visibility of inherited members, when each form is appropriate, and why public inheritance is the most common choice for modeling an “is-a” relationship.
Concept
Inheritance lets one class reuse and extend another class.
In C++, when you inherit from a base class, you must choose an inheritance mode:
class Derived : public Base { };
class Derived : protected Base { };
class Derived : private Base { };
This choice does not change the base class itself. Instead, it changes how the base class's members are viewed through the derived class.
The key idea
C++ member access already has three levels inside a class:
public: accessible from anywhereprotected: accessible inside the class and its derived classesprivate: accessible only inside the class itself
Inheritance adds another layer:
- public inheritance keeps the base class interface public/protected in the derived class
- protected inheritance makes the base class's public and protected members protected in the derived class
- private inheritance makes the base class's public and protected members private in the derived class
Important rule:
- Base class
privatemembers are never directly accessible in the derived class, no matter which inheritance mode you choose.
Why this matters
Inheritance is not just about code reuse. It also expresses a relationship between types.
- Public inheritance means:
Derivedis aBase - Private inheritance is more like:
Derivedis implemented in terms ofBase - Protected inheritance is a specialized form that hides the base interface from users of the derived class, but still exposes it to further derived classes
This affects:
- whether a
Derivedobject can be used where aBaseobject is expected - what outside code is allowed to call
- how much of the base API becomes part of the derived API
Access transformation table
Assume Base contains:
- a
publicmember - a
protectedmember - a
privatemember
Then inside Derived, the inherited access becomes:
| Inheritance mode | Base public becomes | Base protected becomes | Base private becomes |
|---|---|---|---|
public | public | protected | not directly accessible |
protected | protected | protected | not directly accessible |
private | private | private | not directly accessible |
Substitutability
With public inheritance, a Derived object can be treated as a Base object:
class Animal {};
class Dog : public Animal {};
Animal* a = new Dog(); // OK
With private or protected inheritance, that conversion is not generally allowed from outside the class:
class Animal {};
class Dog : private Animal {};
Animal* a = new Dog(); // Error
That is why public inheritance is usually used for object-oriented modeling, while private inheritance is used much less often.
Mental Model
Think of a base class as a toolbox and the derived class as a worker who receives that toolbox.
- With public inheritance, the worker keeps the same tools visible to everyone. Other people can still ask the worker to use the tools in the same public way.
- With protected inheritance, the worker keeps the tools available for themselves and for future apprentices, but not for the public.
- With private inheritance, the worker takes the tools into a private workshop. They can use them internally, but outsiders cannot access them through the worker.
Another way to remember it:
- public inheritance = open relationship
- protected inheritance = family-only relationship
- private inheritance = internal implementation detail
Syntax and Examples
Basic syntax
class Base {
public:
void publicMethod() {}
protected:
void protectedMethod() {}
private:
void privateMethod() {}
};
class PublicDerived : public Base {};
class ProtectedDerived : protected Base {};
class PrivateDerived : private Base {};
Example: how access changes
#include <iostream>
using namespace std;
class Base {
public:
void showPublic() {
cout << "Base public" << endl;
}
protected:
void showProtected() {
cout << "Base protected" << endl;
}
};
class PublicDerived : public Base {
public:
void test() {
showPublic(); // OK
showProtected(); // OK
}
};
class ProtectedDerived : protected Base {
public:
void test() {
showPublic(); // OK inside the class
showProtected(); // OK inside the class
}
};
class PrivateDerived : private Base {
public:
void test() {
showPublic(); // OK inside the class
showProtected(); // OK inside the class
}
};
int main() {
PublicDerived a;
a.showPublic(); // OK: still public
ProtectedDerived b;
// b.showPublic(); // Error: became protected
PrivateDerived c;
// c.showPublic(); // Error: became private
}
What this example shows
PublicDerivedexposesBase's public members as public.ProtectedDerivedhidesBase's public members from outside code.PrivateDerivedhides them even more strongly.- In all three derived classes, inherited
publicandprotectedmembers can still be used inside the derived class itself. privatemembers ofBaseare never directly available in the derived class.
Step by Step Execution
Consider this example:
#include <iostream>
using namespace std;
class Base {
public:
void greet() {
cout << "Hello from Base" << endl;
}
};
class Derived : protected Base {
public:
void callGreet() {
greet();
}
};
int main() {
Derived d;
d.callGreet();
// d.greet(); // Error
}
Step by step
Basedefines a public method namedgreet().Derived : protected BasemeansBase's public members becomeprotectedinsideDerived.- Inside
Derived, the methodcallGreet()can callgreet()because protected members are accessible from within the class. - In
main(),d.callGreet()works becausecallGreet()is public inDerived. d.greet()fails becausegreet()is no longer public throughDerived; it became protected due to protected inheritance.
Result
The base functionality still exists inside Derived, but it is no longer part of Derived's public interface.
Real World Use Cases
Public inheritance
Used when the derived type should truly behave like the base type.
Examples:
Doginherits fromAnimalTextBoxinherits fromWidgetCircleinherits fromShape- custom exception types inheriting from
std::exception
Private inheritance
Used when a class wants to reuse implementation from another class without exposing that class as part of its public API.
Examples:
- using a helper base class for low-level behavior
- inheriting from an empty policy class for optimization
- internal framework classes where the base should not be visible to API users
In many cases, composition is preferred instead:
class Engine {
public:
void start() {}
};
class Car {
private:
Engine engine;
public:
void start() {
engine.start();
}
};
Protected inheritance
Less common in day-to-day code.
Possible uses:
- library internals
- framework extension points
- cases where a base interface should be available only to subclasses, not to users of the class
Most beginners will use public inheritance far more often than protected or private inheritance.
Real Codebase Usage
In real projects, developers usually follow this rule:
- use public inheritance for polymorphism and clear type hierarchies
- prefer composition over private inheritance for implementation reuse
- use protected inheritance only when there is a strong design reason
Common patterns
1. Public inheritance for polymorphic interfaces
class Logger {
public:
virtual void log(const std::string& msg) = 0;
virtual ~Logger() = default;
};
class FileLogger : public Logger {
public:
void log(const std::string& msg) override {
// write to file
}
};
This allows code to depend on Logger while using different derived implementations.
2. Private inheritance for implementation details
class Helper {
protected:
void doWork() {}
};
class Manager : private Helper {
public:
void run() {
doWork();
}
};
Here, Manager uses Helper internally, but users of Manager do not see Helper's interface.
3. Re-exposing selected members with using
A derived class can change what it exposes:
class Base {
public:
void foo() {}
};
class Derived : private Base {
public:
using Base::foo;
};
Now foo() becomes publicly available again through Derived.
Design guidance
Ask this question before inheriting:
Should every
Derivedobject be usable wherever aBaseobject is expected?
- If yes, public inheritance may be correct.
- If no, inheritance may be the wrong tool, or private inheritance may be an implementation choice.
- If you just want to contain and use another object, composition is often simpler and clearer.
Common Mistakes
1. Thinking inheritance mode affects access inside the base class
It does not. It only affects how base members are seen through the derived class.
2. Expecting base private members to be accessible in derived classes
Broken example:
class Base {
private:
int x;
};
class Derived : public Base {
public:
void test() {
// x = 10; // Error
}
};
Fix
Use protected members carefully, or better, provide public/protected getter and setter functions when needed.
class Base {
private:
int x = 0;
protected:
void setX(int value) {
x = value;
}
};
3. Using private inheritance when composition is clearer
Broken design idea:
class Car : private Engine {
};
This suggests Car is built from Engine internals through inheritance, but conceptually a car has an engine, not is implemented as an engine.
Better
class Engine {
public:
void start() {}
};
class Car {
private:
Engine engine;
};
4. Assuming protected inheritance is common
It exists, but it is relatively rare. Beginners often overuse it while experimenting. In many cases, public inheritance or composition is a better fit.
5. Forgetting conversion rules
Broken example:
class Base {};
class Derived : private Base {};
int main() {
// Base* ptr = new Derived(); // Error
}
Why it fails
Outside the class, Derived is not publicly a Base when private inheritance is used.
6. Exposing too much through public inheritance
If the derived class should not offer the full base API to users, public inheritance may be the wrong choice.
Always choose inheritance mode based on the relationship you want to model, not just on code reuse.
Comparisons
| Feature | Public inheritance | Protected inheritance | Private inheritance |
|---|---|---|---|
| Base public members become | public | protected | private |
| Base protected members become | protected | protected | private |
| Base private members become | not directly accessible | not directly accessible | not directly accessible |
| Can derived use base public/protected members internally? | Yes | Yes | Yes |
| Can outside code use base public members through derived object? | Yes | No | No |
Can Derived convert to Base publicly? | Yes | No, not from outside | No, not from outside |
| Typical meaning | is-a | restricted is-a for subclasses | implemented-in-terms-of |
| Commonness | Very common | Rare | Uncommon |
Inheritance vs composition
| Approach | Meaning | Best for |
|---|---|---|
| Public inheritance | Derived is a kind of Base | polymorphism, shared interface |
| Private inheritance | Derived uses Base internally through inheritance | special implementation reuse |
| Composition | Derived has a Base-like object | most reuse scenarios |
In modern C++ design, composition is often preferred unless the relationship is clearly an is-a relationship.
Cheat Sheet
class D1 : public Base {};
class D2 : protected Base {};
class D3 : private Base {};
Access transformation
publicinheritance:- base
public->public - base
protected->protected
- base
protectedinheritance:- base
public->protected - base
protected->protected
- base
privateinheritance:- base
public->private - base
protected->private
- base
- base
private-> never directly accessible in derived
Quick rules
- Use public inheritance for an is-a relationship.
- Use composition when one class simply uses another.
- Use private inheritance only for special implementation reuse.
- Use protected inheritance rarely.
- Only public inheritance gives normal public
Derived -> Baseconversion.
Handy reminder
publicinheritance keeps the base interface visible.protectedinheritance hides it from users but not subclasses.privateinheritance hides it from everyone except the derived class itself.
Re-expose a hidden member
class Derived : private Base {
public:
using Base::foo;
};
Important edge case
Even with public inheritance, derived classes still cannot directly access base private data members.
FAQ
When should I use public inheritance in C++?
Use public inheritance when the derived class truly is a specialized version of the base class and should be usable anywhere the base class is expected.
Can a derived class access private members of the base class?
No. Base class private members are never directly accessible in derived classes, regardless of inheritance mode.
Is private inheritance the same as composition?
No. Private inheritance still creates an inheritance relationship internally. Composition means storing another object as a member. Composition is often clearer.
Why is protected inheritance rarely used?
Because it solves a narrow design problem: exposing base functionality to subclasses but not to users. Most code is better served by public inheritance or composition.
Can I convert a privately derived object to a base pointer?
Not from normal outside code. Private inheritance hides that conversion.
Does inheritance mode affect virtual functions?
The inheritance mode affects accessibility and conversion rules, not whether virtual dispatch works inside the hierarchy.
Can I make a hidden inherited member public again?
Yes. You can re-expose selected base members using a using declaration in the derived class.
Mini Project
Description
Build a small class hierarchy that demonstrates all three inheritance modes. The project helps you see which functions remain publicly accessible, which become hidden, and how a derived class can still use inherited functionality internally.
Goal
Create a C++ program that compares public, protected, and private inheritance using one base class and three derived classes.
Requirements
- Create a base class with one public method and one protected method.
- Create three derived classes using public, protected, and private inheritance.
- In each derived class, add a public method that calls the inherited methods internally.
- In
main(), test which methods are callable from outside the class. - Include comments showing which lines would cause compilation errors if uncommented.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.