Public, protected and private inheritance, base class initialization, overriding, slicing, multiple inheritance, and object initialization rules.
Inheritance models an "is-a" relationship between classes. A derived class inherits the interface and (optionally) the implementation of its base class.
// Syntax: class Derived : access-specifier Base
class Student {
public:
void GetAge();
};
class Ramy : public Student {
// Ramy "is a" Student — inherits GetAge()
};
The access specifier defaults to private for classes and public for structs. In practice, public inheritance is by far the most common.
Inherited classes share public data members and functions. Use the protected keyword to specify that certain attributes should only be accessible by class members — not external code, but visible to derived classes.
| Base member | In public inheritance | In protected inheritance | In private inheritance |
|---|---|---|---|
public | public | protected | private |
protected | protected | protected | private |
private | inaccessible | inaccessible | inaccessible |
protected members effectively private to the outside world while still accessible within the hierarchy is to use public inheritance and mark the members protected in the base class.If the base class has no default constructor, you must explicitly initialize it from the derived constructor's member initializer list:
class Base {
public:
Base(int x) { /* ... */ }
};
class Derived : public Base {
public:
Derived() : Base(25) { // passes 25 up to Base's constructor
// ...
}
};
protected members effectively private — initialize them through the base constructor and expose no setters.If a derived class implements the same function with the same signature, it overrides the base version. The base version can still be called explicitly with the scope operator:
class Student {
public:
void GetAge();
};
class Ramy : public Student {
public:
void GetAge(); // overrides Student::GetAge
};
Ramy me;
me.GetAge(); // calls Ramy's version
me.Student::GetAge(); // explicitly calls Student's version
Models a "has-a" relationship. The derived class inherits everything from the base, but all inherited attributes are treated as private within the derived class — nothing is exposed to the outside or further derived classes.
class Ramy : private Student {
// All public/protected Student members become private here
// External code and further subclasses cannot access them
};
When a derived object is assigned to a base class variable by value, the derived-class-specific data is "sliced off" — only the base portion is copied:
Ramy me;
Student s = me; // SLICED — Ramy-specific members are lost
Student* ptr = &me; // safe — no slicing, points to full Ramy object
Student& ref = me; // safe — reference to full Ramy object
finalA class can inherit from more than one base class. Bases are constructed in the order they are listed:
class Ramy : public Student, public Husband {
// Student constructed first, then Husband
};
virtual inheritance as the fix.final ClassesAdding final to a class declaration prevents it from being used as a base class:
class Ramy final : public Student {
// No class can inherit from Ramy
};
When a derived class overrides any overload of a function name, the compiler hides all other overloads from the base:
class Base {
public:
virtual void f1();
virtual void f1(int x);
};
class Derived : public Base {
public:
virtual void f1(); // override
};
Derived d;
d.f1(); // OK — uses Derived's version
d.f1(5); // ERROR — Base's f1(int) is hidden!
Fix it with a using declaration to bring all base overloads into scope:
class Derived : public Base {
public:
using Base::f1; // compiler now sees all f1 overloads from Base
virtual void f1(); // override
};
Be intentional about what your inherited methods are saying to derived classes:
| Function type | What it means to derived classes |
|---|---|
Pure virtual (= 0) | Inherit the interface only. You must provide an implementation. |
| Virtual | Inherit the interface + default implementation. You may override. |
| Non-virtual | Inherit the interface + mandatory implementation. Do not redefine. |
The compiler auto-generates copy constructors and assignment operators if needed, but they make no attempt to copy base class data members. You must explicitly call the base version:
Derived::Derived(const Derived& other) : Base(other) {
// Base(other) invokes Base's copy constructor
// then copy the Derived-specific members here
}
Derived& Derived::operator=(const Derived& other) {
Base::operator=(other); // invoke Base's assignment
// then assign Derived-specific members
return *this;
}