Understanding Access Control in C++ Inheritance: Public, Protected, and Private Inheritance

Nov 02, 2025 · Programming · 19 views · 7.8

Keywords: C++ Inheritance | Access Control | Public Inheritance | Protected Inheritance | Private Inheritance | Object-Oriented Programming

Abstract: This article provides an in-depth exploration of the three inheritance modes in C++. Through detailed code examples and access permission analysis, it explains how public inheritance maintains base class access levels, protected inheritance downgrades base class public and protected members to protected, and private inheritance downgrades all accessible members to private. The article also discusses the philosophical significance of inheritance and practical engineering trade-offs, helping developers choose appropriate inheritance methods based on specific requirements.

Introduction

In object-oriented programming, inheritance is a core mechanism for achieving code reuse and polymorphism. C++ provides three different inheritance modes: public, protected, and private, which determine the access permissions of base class members in derived classes. Understanding the differences between these inheritance modes is crucial for designing robust class hierarchies.

Fundamentals of Member Access Permissions

Before delving into inheritance, we first need to understand the three access modifiers in C++:

class Base {
public:
    int publicMember;    // Accessible by any code that knows Base
protected:
    int protectedMember; // Accessible only by Base and its derived classes
private:
    int privateMember;   // Accessible only by Base itself
};

Here, "knows" means "acknowledges the existence of and can access." Public members are visible to all code, protected members are visible to derived classes, and private members are visible only to the class that defines them.

Inheritance Access Control Mechanism

Inheritance access control determines the visibility of base class members in derived classes. Consider base class Base and derived class Child:

class Base {
public:
    int x;
protected:
    int y;
private:
    int z;
};

class PublicChild : public Base {
    // x remains public
    // y remains protected
    // z is inaccessible
};

class ProtectedChild : protected Base {
    // x becomes protected
    // y remains protected
    // z is inaccessible
};

class PrivateChild : private Base {
    // x becomes private
    // y becomes private
    // z is inaccessible
};

Important note: All derived classes contain x, y, and z variables; the difference lies only in access permissions.

Detailed Analysis of Public Inheritance

Public inheritance is the most commonly used inheritance mode, establishing an "is-a" relationship. In public inheritance:

class PublicDerived : public Base {
public:
    int getProtected() { return y; }  // Can access protected member
    // int getPrivate() { return z; }  // Error: z is inaccessible
};

int main() {
    PublicDerived obj;
    cout << obj.x;           // Correct: x is public
    // cout << obj.y;        // Error: y is protected
    // cout << obj.z;        // Error: z is inaccessible
    cout << obj.getProtected(); // Correct: access via public method
}

Public inheritance maintains the base class access levels: public members remain public, and protected members remain protected. This inheritance mode supports polymorphism, allowing safe conversion of derived class pointers to base class pointers.

Analysis of Protected Inheritance

Protected inheritance downgrades both public and protected members of the base class to protected:

class ProtectedDerived : protected Base {
public:
    int getPublic() { return x; }     // x is now protected
    int getProtected() { return y; }  // y remains protected
};

int main() {
    ProtectedDerived obj;
    // cout << obj.x;        // Error: x is now protected
    cout << obj.getPublic();   // Correct: access via public method
    cout << obj.getProtected(); // Correct: access via public method
}

Protected inheritance restricts external code access to base class members but allows further derived classes to access these members. This inheritance mode is used when you need to hide the base class interface while allowing inheritance hierarchy extension.

In-depth Discussion of Private Inheritance

Private inheritance downgrades all accessible members of the base class to private:

class PrivateDerived : private Base {
public:
    int getPublic() { return x; }     // x is now private
    int getProtected() { return y; }  // y is now private
};

class FurtherDerived : public PrivateDerived {
public:
    // int getBaseX() { return x; }   // Error: x is private in PrivateDerived
    // int getBaseY() { return y; }   // Error: y is private in PrivateDerived
};

int main() {
    PrivateDerived obj;
    // cout << obj.x;        // Error: x is now private
    cout << obj.getPublic();   // Correct: access via public method
}

Private inheritance implements an "is-implemented-in-terms-of" relationship, primarily used as an alternative to composition. It completely hides the inheritance relationship, preventing further derived classes from accessing base class members.

Philosophical and Practical Considerations of Inheritance

From an object-oriented perspective, inheritance abstracts the "kind-of" relationship. For example, Car is a kind of Vehicle, expressed through public inheritance. The part-of relationship is typically implemented through composition.

In practical engineering, choosing inheritance modes requires consideration of:

// Design decision example
class Stack : private std::vector<int> {
    // Using private inheritance to implement "stack implemented using vector"
public:
    void push(int value) { this->push_back(value); }
    int pop() {
        int value = this->back();
        this->pop_back();
        return value;
    }
    bool empty() const { return this->empty(); }
};

This example demonstrates a typical use of private inheritance: reusing implementation without exposing the base class interface.

Engineering Practices for Access Permissions

There is debate about whether to use protected data members. The key consideration is return on investment:

// Stable protected interface design
class StableBase {
private:
    std::string internal_data;
protected:
    // Stable protected interface
    const std::string& getData() const { return internal_data; }
    void setData(const std::string& data) { internal_data = data; }
public:
    // Stable public interface
    virtual void process() = 0;
};

Type Conversion and Inheritance Relationships

Only public inheritance supports safe upcasting:

class Vehicle {};
class Car : public Vehicle {};
class SecretCar : private Vehicle {};

void processVehicle(Vehicle* v) {}

int main() {
    Car car;
    processVehicle(&car);  // Correct: public inheritance
    
    SecretCar secret;
    // processVehicle(&secret);  // Error: private inheritance
}

This type safety mechanism ensures that only genuine "is-a" relationships permit pointer conversion.

Conclusion

C++'s three inheritance modes provide different levels of encapsulation control. Public inheritance establishes type hierarchies, protected inheritance restricts interface exposure while allowing extension, and private inheritance completely hides implementation details. Choosing appropriate inheritance modes requires considering class design intent, future extension needs, and maintenance costs. In practical projects, decisions should be based on specific business requirements and technical constraints rather than blindly following dogma.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.