C++ Inheritance Explained: Access Control, Object Slicing, Default Functions, and Composition vs Inheritance

Deep Dive into C++ Inheritance: Access Control, Object Slicing, Default Functions, and Composition Design

This article focuses on the syntax, access control, and object model of C++ inheritance. It explains the real behavior behind code reuse, object slicing, name hiding, and default member functions, and clarifies the design boundary between inheritance and composition. Keywords: C++ inheritance, object slicing, composition over inheritance.

Technical Snapshot

Parameter Description
Language C++
License CC 4.0 BY-SA (as declared in the original article)
Stars Not provided
Core Dependencies iostream, string, vector

Inheritance is a core mechanism for object-oriented code reuse.

Inheritance lets you extend new capabilities while preserving the existing properties and behavior of a base class. The primary problem it solves is duplicate modeling: when both Student and Teacher contain fields such as name, phone number, age, and identity information, implementing them separately creates a large amount of duplicated code.

A more reasonable approach is to extract a Person base class, move shared data and common behavior upward, and let Student and Teacher keep only their own differences as derived classes. This design improves reuse, maintainability, and the expressiveness of the model.

#include 
<iostream>
#include 
<string>
using namespace std;

class Person {
public:
    void identity() {
        cout << "Identity verification: " << _name << endl; // Call a public base-class behavior
    }
protected:
    string _name = "张三"; // Move shared attributes into the base class
    int _age = 18;
};

class Student : public Person {
public:
    void study() { cout << "study" << endl; } // Extend behavior in the derived class
};

This code demonstrates the minimal value of inheritance: extracting shared members and reducing repeated definitions.

The inheritance mode determines the visibility of base-class members in the derived class.

C++ supports three inheritance modes: public, protected, and private. Regardless of which one you use, the base class’s private members are still inherited into the object, but they are not visible to the derived class at the language level and cannot be accessed directly.

In practice, public inheritance is the most common choice because it expresses an “is-a” relationship most clearly. class defaults to private inheritance, while struct defaults to public inheritance. In production code, however, you should always write the inheritance mode explicitly to avoid confusion.

Inheritance relationship diagram AI Visual Insight: The diagram shows the hierarchy between the base class Person and the derived class Student, emphasizing that a derived object contains a complete base-class subobject. That is the prerequisite for access control rules, construction/destruction order, and object slicing.

You can summarize the access rule as “whichever is more restrictive.”

The access level of a base-class member inside a derived class can be understood as the more restrictive result between the member’s original access level and the inheritance mode. Here, public > protected > private.

class Person {
private:
    string _name = "张三"; // Private member: not directly accessible in the derived class
protected:
    int _age = 20;          // Protected member: accessible in the derived class
};

class Student : public Person {
public:
    void test() {
        _age = 18;          // Valid: access an inherited protected member
        // _name = "小明";   // Invalid: a private base-class member is not visible
    }
};

This example confirms that private members are inherited, but the derived class cannot use them directly.

Template inheritance is affected by on-demand instantiation rules.

When the base class itself is a class template, the derived class often needs to explicitly qualify base-class members with the class scope, such as `vector

::push_back(x)`. The root cause is not that inheritance fails, but that templates use on-demand instantiation, so the compiler does not resolve dependent names in advance. “`cpp #include using namespace std; template class Stack : public vector { public: void push(const T& x) { vector ::push_back(x); // Explicitly qualify the base-class scope to avoid dependent-name lookup failure } void pop() { vector ::pop_back(); // Call a member of the template base class } }; “` This code shows that the key issue in template inheritance is not syntax, but name lookup rules. ## Public inheritance supports safe conversion from a derived class to a base class. A derived object can be assigned to a base object, base reference, or base pointer. This process is often referred to as object slicing. In essence, the base type receives only the base-class subobject portion of the derived object, while the additional members introduced by the derived class are discarded. ![Object slicing diagram](https://i-blog.csdnimg.cn/direct/2153bcc9f9a94e4aacbe5fd011ec8dd8.png) **AI Visual Insight:** The image illustrates that when `Student` is converted to `Person`, only the base-class subobject is preserved. The right side typically represents pointer or reference binding, while the left side represents slicing through value copy, reinforcing the idea that the base-class view can see only the base-class portion. “`cpp class Person { protected: string _name; }; class Student : public Person { public: int _No; }; void demo() { Student s; Person* pp = &s; // Upcast: safe Person& rp = s; // Upcast: safe Person p = s; // Value conversion: slicing occurs } “` This example demonstrates the compatibility conversion rules in an inheritance hierarchy. ### Conversion from a base class to a derived class cannot happen automatically. Converting a base-class pointer to a derived-class pointer requires an explicit cast, and it is safe only when that base pointer actually points to a derived object. If the base class is polymorphic, prefer `dynamic_cast` to validate the conversion at runtime. ## Members with the same name in an inheritance hierarchy trigger name hiding. Base and derived classes have independent scopes. As soon as the derived class defines a member function with the same name as one in the base class, the base-class function is hidden, regardless of whether the parameter list is the same. This is not overloading. “`cpp #include using namespace std; class A { public: void fun() { cout