Skip to content

C++ OOP — Level 2 Complete Notes

Inheritance, Constructor/Destructor Order, Function Inheritance, Method Hiding, Access Modes, Diamond Problem & Virtual Inheritance


1. Inheritance

1.1 What is Inheritance?

Inheritance is an OOP mechanism where a derived class acquires properties and behavior from a base class.

Terminology:

  • Base class = Parent class / Superclass
  • Derived class = Child class / Subclass

Basic syntax:

class Derived : public Base {
};

Example:

class Animal {
public:
    void eat() {
        cout << "Eating\n";
    }
};

class Dog : public Animal {
public:
    void bark() {
        cout << "Barking\n";
    }
};

Now:

Dog d;

d.eat();   // inherited
d.bark();  // own function

Output:

Eating
Barking

Conceptually:

Animal
 └── eat()

Dog
 ├── eat()    ← inherited
 └── bark()   ← own

1.2 Why Use Inheritance?

Main purposes:

  1. Code reuse
  2. Extending existing functionality
  3. Establishing an is-a relationship
  4. Foundation for runtime polymorphism

Example:

Dog is an Animal
Car is a Vehicle
Circle is a Shape

2. Types of Inheritance

There are five commonly discussed structural types.


2.1 Single Inheritance

One base → one derived.

A
↓
B
class B : public A {
};

2.2 Multilevel Inheritance

Inheritance occurs across multiple levels.

A
↓
B
↓
C

Example:

class B : public A {
};

class C : public B {
};

C indirectly inherits from A through B.


2.3 Hierarchical Inheritance

One base has multiple derived classes.

      A
     / \
    B   C

Example:

class B : public A {
};

class C : public A {
};

2.4 Multiple Inheritance

One derived class inherits from multiple base classes.

A     B
 \   /
   C

Example:

class C : public A, public B {
};

Potential problem:

class A {
public:
    void fun() {
        cout << "A";
    }
};

class B {
public:
    void fun() {
        cout << "B";
    }
};

class C : public A, public B {
};

C obj;
obj.fun();   // ❌ ambiguous

C++ doesn't know whether you mean:

obj.A::fun();

or:

obj.B::fun();

2.5 Hybrid Inheritance

Combination of multiple types of inheritance.

A common example is the diamond:

      A
     / \
    B   C
     \ /
      D

This combines hierarchical and multiple inheritance.


3. Inheritance Type vs Inheritance Access Mode

These are different concepts.

Type of inheritance

Describes the structure:

Single
Multilevel
Multiple
Hierarchical
Hybrid

Access mode

Describes how inherited public and protected members are exposed:

class B : public A
class B : protected A
class B : private A

For example:

class B : private A

can still be single inheritance.

private here describes the access mode, not the structural type.


4. is-a vs has-a

Inheritance usually represents:

is-a

Example:

Dog is an Animal

Composition represents:

has-a

Example:

Car has an Engine

So:

class Dog : public Animal

represents an is-a relationship.

Whereas:

class Car {
    Engine engine;
};

represents a has-a relationship.


5. Constructor Order in Inheritance

This is one of the most important C++ placement topics.

For:

A → B → C

where:

class B : public A
class C : public B

and:

C obj;

construction happens:

A constructor
↓
B constructor
↓
C constructor

Golden Rule

Construction: Base → Derived


5.1 Why?

A derived object contains a base-class subobject.

The base portion must be initialized before the derived portion can be initialized.

Example:

class Animal {
public:
    Animal() {
        cout << "Animal\n";
    }
};

class Dog : public Animal {
public:
    Dog() {
        cout << "Dog\n";
    }
};
Dog d;

Output:

Animal
Dog

6. Destructor Order in Inheritance

Destruction happens in the exact reverse order.

For:

A → B → C

construction:

A → B → C

destruction:

~C → ~B → ~A

Golden Rule

Destruction: Derived → Base

Example:

class A {
public:
    A() {
        cout << "A ";
    }

    ~A() {
        cout << "~A ";
    }
};

class B : public A {
public:
    B() {
        cout << "B ";
    }

    ~B() {
        cout << "~B ";
    }
};

class C : public B {
public:
    C() {
        cout << "C ";
    }

    ~C() {
        cout << "~C ";
    }
};
C obj;

Output:

A B C ~C ~B ~A

7. Parameterized Base Constructor

A derived constructor must ensure that its base class can be constructed.

Consider:

class Base {
public:
    Base(int x) {
        cout << x;
    }
};

class Derived : public Base {
public:
    Derived() {
        cout << "Derived";
    }
};

This:

Derived obj;

causes a compilation error.

Why?

The compiler needs to construct the Base portion first.

It effectively needs:

Base();

But Base only has:

Base(int x);

and no default constructor exists.


8. Calling Base Constructor from Derived

Use the constructor initializer list:

class Base {
public:
    Base(int x) {
        cout << "Base " << x << "\n";
    }
};

class Derived : public Base {
public:
    Derived() : Base(10) {
        cout << "Derived\n";
    }
};

Now:

Derived obj;

works.

Execution:

Base(10)
Derived()

Output:

Base 10
Derived

Important:

Derived() : Base(10)

means:

Construct the Base part using Base(10) before executing the body of Derived().


9. General Construction Order

For a normal inheritance situation:

1. Base classes
2. Data members
3. Constructor body

Example:

class A {
public:
    A() {
        cout << "A ";
    }
};

class B {
public:
    B() {
        cout << "B ";
    }
};

class C : public A {
    B b;

public:
    C() {
        cout << "C ";
    }
};
C obj;

Output:

A B C

Because:

A → B → C

10. Member Initialization Order

A major C++ trap:

Data members are initialized in the order they are declared, not the order written in the initializer list.

Example:

class A {
    int x;
    int y;

public:
    A() : y(20), x(10) {
        cout << x << " " << y;
    }
};

Even though the initializer list says:

y first
x second

the declaration is:

int x;
int y;

Therefore:

x → 10
y → 20

Output:

10 20

Placement Rule

Declaration order beats initializer-list order.


11. Multiple Inheritance Constructor Order

Consider:

class A {
public:
    A() {
        cout << "A ";
    }
};

class B {
public:
    B() {
        cout << "B ";
    }
};

class C : public A, public B {
public:
    C() {
        cout << "C ";
    }
};

Creation:

C obj;

Output:

A B C

because the inheritance declaration is:

class C : public A, public B

Therefore:

A → B → C

12. Initializer List Does NOT Change Base Order

Consider:

class C : public A, public B {
public:
    C() : B(), A() {
        cout << "C ";
    }
};

It may look like:

B → A → C

but that's wrong.

Base classes are initialized according to their order in the inheritance declaration:

class C : public A, public B

Therefore:

A → B → C

Important

Base-class initialization order = order in inheritance declaration.


13. Complete Destruction Order

Construction:

Base classes
↓
Data members
↓
Derived constructor body

Destruction:

Derived destructor body
↓
Data members in reverse declaration order
↓
Base classes in reverse construction order

For:

class C : public A {
    B b;
};

construction:

A → B → C

destruction:

~C → ~B → ~A

14. Function Inheritance

A derived class can use accessible functions from its base class.

Example:

class Animal {
public:
    void eat() {
        cout << "Eating\n";
    }
};

class Dog : public Animal {
public:
    void bark() {
        cout << "Barking\n";
    }
};

Now:

Dog d;

d.eat();
d.bark();

Both are valid.


15. Private, Protected and Public Functions in Inheritance

Suppose:

class Animal {
private:
    void eat();

protected:
    void sleep();

public:
    void run();
};

Inside the derived class:

eat()   → ❌
sleep() → ✅
run()   → ✅

So:

Base member Directly accessible in Derived?
private ❌
protected ✅
public ✅

16. protected

protected is accessible:

  • inside the class itself
  • inside derived classes

but not through ordinary outside access.

Example:

class Animal {
protected:
    void eat() {
        cout << "Eating";
    }
};

class Dog : public Animal {
public:
    void test() {
        eat();   // ✅
    }
};

But:

Dog d;
d.eat();        // ❌

17. Inheritance Access Modes

There are three:

public
protected
private

Example:

class Derived : public Base

or:

class Derived : protected Base

or:

class Derived : private Base

These determine how inherited public and protected members are exposed through the derived class.


18. The Most Important Access Table

Base member Public inheritance Protected inheritance Private inheritance
public public protected private
protected protected protected private
private inaccessible inaccessible inaccessible

Critical rule

Base private members never become directly accessible in the derived class, regardless of inheritance mode.


19. Example of Private Inheritance

class A {
public:
    int x = 10;

protected:
    int y = 20;

private:
    int z = 30;
};

class B : private A {
public:
    void test() {
        cout << x;  // ✅
        cout << y;  // ✅
        // cout << z; // ❌
    }
};

Inside B:

x → accessible
y → accessible
z → inaccessible

But outside:

B obj;

cout << obj.x;

is an error because:

class B : private A

causes:

A public    → B private
A protected → B private

20. Public vs Protected vs Private Inheritance

Public inheritance

Usually represents:

is-a

Example:

class Dog : public Animal

A Dog is an Animal.

Protected inheritance

Base's public/protected interface becomes protected in the derived class.

Private inheritance

Base's public/protected interface becomes private in the derived class.

Conceptually:

Public inheritance:
public    → public
protected → protected

Protected inheritance:
public    → protected
protected → protected

Private inheritance:
public    → private
protected → private

21. Method Hiding

Suppose:

class Base {
public:
    void fun() {
        cout << "Base";
    }
};

class Derived : public Base {
public:
    void fun() {
        cout << "Derived";
    }
};

Then:

Derived d;
d.fun();

Output:

Derived

The derived declaration hides the base declaration with the same name.

This is called:

Method hiding / Name hiding


22. Hiding Is Not Overriding

The previous example does not automatically mean overriding.

class Base {
public:
    void fun() {
        cout << "Base";
    }
};

class Derived : public Base {
public:
    void fun() {
        cout << "Derived";
    }
};

Since Base::fun() is not virtual, this is simply name hiding.

Runtime overriding will be covered with virtual functions.


23. Entire Overload Set Can Be Hidden

This is a major placement trap.

class Base {
public:
    void fun() {
        cout << "Base";
    }

    void fun(int x) {
        cout << "Base Int";
    }
};

class Derived : public Base {
public:
    void fun() {
        cout << "Derived";
    }
};

Now:

Derived d;

d.fun();

works:

Derived

But:

d.fun(10);

produces a compilation error.

Why?

Because declaring Derived::fun() hides the entire fun name from the Base class during normal name lookup.

It doesn't matter that the parameter list is different.


24. Bringing Base Overloads Back

Use:

using Base::fun;

Example:

class Derived : public Base {
public:
    using Base::fun;

    void fun() {
        cout << "Derived";
    }
};

Now:

Derived d;

d.fun();
d.fun(10);

works.

Output:

Derived
Base Int

So:

using Base::fun;

brings the base overloads back into scope.


25. Method Hiding Placement Rules

Remember:

Derived declares function with same name
                ↓
Base functions with that name become hidden

Even:

Base::fun()
Base::fun(int)
Base::fun(string)

can all be hidden by:

Derived::fun()

To restore them:

using Base::fun;

26. Diamond Problem

The diamond structure:

        A
       / \
      B   C
       \ /
        D

Code:

class A {
public:
    int x = 10;
};

class B : public A {
};

class C : public A {
};

class D : public B, public C {
};

27. Why Does the Diamond Cause a Problem?

Without virtual inheritance, D contains two copies of A.

Conceptually:

D
├── B
│   └── A
│       └── x
│
└── C
    └── A
        └── x

Therefore there are two separate A::x members.


28. Ambiguity in the Diamond

Now:

D obj;

cout << obj.x;

Which x?

There are two:

B → A → x
C → A → x

Therefore:

❌ Ambiguous

29. Resolving Ambiguity Explicitly

You can specify the path:

cout << obj.B::x;

or:

cout << obj.C::x;

These refer to different A subobjects.

You can even have:

obj.B::x = 10;
obj.C::x = 20;

because they are separate objects.


30. Virtual Inheritance

To solve the diamond problem:

class B : virtual public A {
};

class C : virtual public A {
};

Complete example:

class A {
public:
    int x = 10;
};

class B : virtual public A {
};

class C : virtual public A {
};

class D : public B, public C {
};

Now D has only one shared A base subobject.

Conceptually:

        A
       / \
      B   C
       \ /
        D

but internally:

D
├── B
├── C
└── shared A

Therefore:

D obj;
cout << obj.x;

is unambiguous.


31. What Does Virtual Inheritance Mean?

This:

class B : virtual public A

does not mean A itself is a "virtual class".

It means:

When a common most-derived object is formed, the virtual base A is shared rather than duplicated along each inheritance path.


32. Normal vs Virtual Diamond

Without virtual inheritance

D
├── B → A₁
└── C → A₂

There are two A subobjects.

With virtual inheritance

D
├── B
├── C
└── shared A

There is one A subobject.


33. Virtual Inheritance Constructor Order

Consider:

class A {
public:
    A() {
        cout << "A ";
    }
};

class B : virtual public A {
public:
    B() {
        cout << "B ";
    }
};

class C : virtual public A {
public:
    C() {
        cout << "C ";
    }
};

class D : public B, public C {
public:
    D() {
        cout << "D ";
    }
};

Creating:

D obj;

produces:

A B C D

Why?

Because:

  1. Virtual base A is constructed first.
  2. B is constructed.
  3. C is constructed.
  4. D is constructed.

And A is constructed only once.


34. Most-Derived Class Controls Virtual Base

This is a very important interview rule.

Consider:

class A {
public:
    A(int x) {
        cout << "A" << x << " ";
    }
};

class B : virtual public A {
public:
    B() : A(10) {
        cout << "B ";
    }
};

class C : virtual public A {
public:
    C() : A(20) {
        cout << "C ";
    }
};

class D : public B, public C {
public:
    D() : A(30) {
        cout << "D ";
    }
};

When:

D obj;

is created, which A constructor runs?

Answer:

A(30)

NOT:

A(10)

and NOT:

A(20)

Why?

Because:

The most-derived class is responsible for constructing the virtual base.

Here, D is the most-derived class.

Therefore:

D → A(30)

controls the virtual base construction.


35. Level 2 Golden Rules

Inheritance

Derived inherits from Base

Inheritance Types

Single
Multilevel
Hierarchical
Multiple
Hybrid

Construction

Base → Members → Derived

Destruction

Derived → Members → Base

Member Initialization

Declaration order

NOT initializer-list order.


Multiple Base Initialization

Inheritance declaration order

NOT initializer-list order.


protected

Class + Derived

but not ordinary outside access.


Inheritance Access

              public    protected    private
Base public   public    protected    private
Base protected protected protected   private
Base private  ❌        ❌           ❌

Method Hiding

Derived function with same name
        ↓
hides Base functions with that name

Restore with:

using Base::fun;

Diamond Problem

Normal inheritance:

two copies of common base

Virtual inheritance:

one shared common base

Virtual Base Construction

Most-derived class controls virtual base

36. Placement Traps Checklist

Before answering an inheritance output question, check these in order:

Trap 1 — Constructor order

Ask:

What is the inheritance hierarchy?

Then construct from base toward derived.


Trap 2 — Destructor order

Reverse the construction order.


Trap 3 — Member order

Look at declaration order, not initializer-list order.


Trap 4 — Multiple inheritance

Look at the order in:

class D : public A, public B

So:

A → B

Trap 5 — Method hiding

If Derived defines:

fun()

then Base's:

fun()
fun(int)
fun(string)

may all be hidden from normal lookup.

Use:

using Base::fun;

to bring them back.


Trap 6 — Private inheritance

Don't confuse:

class B : private A

with:

"A's private members become accessible."

They do not.

Instead:

A public    → B private
A protected → B private
A private   → inaccessible

Trap 7 — Diamond

If you see:

A
/ \
B C
\ /
D

ask:

Is inheritance virtual?

If no:

two A subobjects

If yes:

one shared A subobject

Trap 8 — Virtual base constructor

If virtual inheritance exists:

The most-derived class constructs the virtual base.


37. Output Questions We Solved

Question 1 — Multilevel inheritance

class A {
public:
    A() { cout << "A "; }
    ~A() { cout << "~A "; }
};

class B : public A {
public:
    B() { cout << "B "; }
    ~B() { cout << "~B "; }
};

class C : public B {
public:
    C() { cout << "C "; }
    ~C() { cout << "~C "; }
};

C obj;

Output:

A B C ~C ~B ~A

Question 2 — Base + Member + Derived

class A {
public:
    A() { cout << "A "; }
    ~A() { cout << "~A "; }
};

class B {
public:
    B() { cout << "B "; }
    ~B() { cout << "~B "; }
};

class C : public A {
    B b;

public:
    C() { cout << "C "; }
    ~C() { cout << "~C "; }
};

Output:

A B C ~C ~B ~A

Question 3 — Method hiding

class Base {
public:
    void show() {
        cout << "Base";
    }

    void show(int x) {
        cout << "Base Int";
    }
};

class Derived : public Base {
public:
    void show() {
        cout << "Derived";
    }
};

Derived d;

d.show();
d.show(10);

Result:

d.show();     → Derived
d.show(10);   → Compilation error

because Derived::show() hides the entire show name from Base.


Question 4 — Private inheritance

class A {
public:
    int x = 10;

protected:
    int y = 20;
};

class B : private A {
public:
    void fun() {
        cout << x << " ";
        cout << y << " ";
    }
};

Inside B:

10 20

But:

B obj;
cout << obj.x;

is an error.


Question 5 — Virtual inheritance

class A {
public:
    A() { cout << "A "; }
};

class B : virtual public A {
public:
    B() { cout << "B "; }
};

class C : virtual public A {
public:
    C() { cout << "C "; }
};

class D : public B, public C {
public:
    D() { cout << "D "; }
};

D obj;

Output:

A B C D

and there is only one A subobject.


38. Level 2 Placement Revision Sheet

INHERITANCE
    ↓
Base → Derived

Types:
    Single
    Multilevel
    Hierarchical
    Multiple
    Hybrid


CONSTRUCTION
    ↓
Base classes
    ↓
Members
    ↓
Derived constructor body


DESTRUCTION
    ↓
Derived destructor body
    ↓
Members (reverse declaration order)
    ↓
Base destructors


MEMBER INITIALIZATION
    ↓
Declaration order
NOT initializer-list order


MULTIPLE INHERITANCE
    ↓
Base order = inheritance declaration order


ACCESS MODES
    ↓
public:
    public → public
    protected → protected

protected:
    public → protected
    protected → protected

private:
    public → private
    protected → private

Base private → inaccessible directly


METHOD HIDING
    ↓
Derived same-name function
    ↓
hides Base overloads

Fix:
    using Base::fun;


DIAMOND
    ↓
A
/ \
B C
\ /
D

Normal:
    2 copies of A

Virtual inheritance:
    1 shared A


VIRTUAL BASE
    ↓
Most-derived class constructs it

39. Level 2 Final Mental Model

When you see an inheritance problem, mentally perform these steps:

Step 1

Draw the inheritance hierarchy.

Step 2

Identify whether inheritance is:

single / multilevel / multiple / hierarchical / hybrid

Step 3

For construction:

virtual bases
→ base classes
→ members
→ constructor body

For the basic cases covered in this level, remember:

Base → Members → Derived

Step 4

For destruction:

reverse everything

Step 5

Check access:

private / protected / public

and the inheritance mode.

Step 6

If a derived function has the same name as a base function:

think METHOD HIDING

Check whether:

using Base::fun;

is present.

Step 7

If you see:

A
/ \
B C
\ /
D

immediately ask:

Virtual inheritance?

If no → two copies of A.

If yes → one shared A.

Step 8

If the virtual base has constructors:

most-derived class controls the virtual base

Level 2 — Core Takeaways

The most important things to know for placements are:

  1. Inheritance allows a derived class to acquire accessible behavior/data from a base class.
  2. Five structural types: single, multilevel, hierarchical, multiple, hybrid.
  3. Construction is Base → Derived.
  4. Destruction is Derived → Base.
  5. Data members initialize in declaration order.
  6. Base classes initialize in inheritance-list order.
  7. A parameterized base constructor must be explicitly called if no default constructor exists.
  8. protected is accessible inside the class and derived classes, but not ordinary outside code.
  9. Inheritance access mode changes the visibility of inherited public/protected members.
  10. Base private members remain inaccessible directly to Derived.
  11. A derived function with the same name hides the entire base overload set.
  12. using Base::fun; can bring hidden base overloads back into scope.
  13. Method hiding is different from overriding.
  14. The diamond problem creates two copies of a common base under normal multiple inheritance.
  15. Virtual inheritance creates one shared virtual-base subobject.
  16. Virtual bases are constructed before ordinary bases.
  17. The most-derived class controls construction of a virtual base.