Question

Public vs Private vs Protected Inheritance in C++

cppinheritanceencapsulationaccess-specifierc++-faq

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 anywhere
  • protected: accessible inside the class and its derived classes
  • private: 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 private members 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: Derived is a Base
  • Private inheritance is more like: Derived is implemented in terms of Base
  • 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 Derived object can be used where a Base object 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 public member
  • a protected member
  • a private member

Then inside Derived, the inherited access becomes:

Inheritance modeBase public becomesBase protected becomesBase private becomes
publicpublicprotectednot directly accessible
protectedprotectedprotectednot directly accessible
privateprivateprivatenot 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
Take Quiz

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

  • PublicDerived exposes Base's public members as public.
  • ProtectedDerived hides Base's public members from outside code.
  • PrivateDerived hides them even more strongly.
  • In all three derived classes, inherited public and protected members can still be used inside the derived class itself.
  • private members of Base are 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

  1. Base defines a public method named greet().
  2. Derived : protected Base means Base's public members become protected inside Derived.
  3. Inside Derived, the method callGreet() can call greet() because protected members are accessible from within the class.
  4. In main(), d.callGreet() works because callGreet() is public in Derived.
  5. d.greet() fails because greet() is no longer public through Derived; 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:

  • Dog inherits from Animal
  • TextBox inherits from Widget
  • Circle inherits from Shape
  • 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.

Take Quiz

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 Derived object be usable wherever a Base object 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

FeaturePublic inheritanceProtected inheritancePrivate inheritance
Base public members becomepublicprotectedprivate
Base protected members becomeprotectedprotectedprivate
Base private members becomenot directly accessiblenot directly accessiblenot directly accessible
Can derived use base public/protected members internally?YesYesYes
Can outside code use base public members through derived object?YesNoNo
Can Derived convert to Base publicly?YesNo, not from outsideNo, not from outside
Typical meaningis-arestricted is-a for subclassesimplemented-in-terms-of
CommonnessVery commonRareUncommon

Inheritance vs composition

ApproachMeaningBest for
Public inheritanceDerived is a kind of Basepolymorphism, shared interface
Private inheritanceDerived uses Base internally through inheritancespecial implementation reuse
CompositionDerived has a Base-like objectmost reuse scenarios

In modern C++ design, composition is often preferred unless the relationship is clearly an is-a relationship.

Take Quiz

Cheat Sheet

class D1 : public Base {};
class D2 : protected Base {};
class D3 : private Base {};

Access transformation

  • public inheritance:
    • base public -> public
    • base protected -> protected
  • protected inheritance:
    • base public -> protected
    • base protected -> protected
  • private inheritance:
    • base public -> private
    • base protected -> private
  • 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 -> Base conversion.

Handy reminder

  • public inheritance keeps the base interface visible.
  • protected inheritance hides it from users but not subclasses.
  • private inheritance 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.

Take Quiz

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.
Take Quiz

Keep learning

Related questions

View more