3.2 Ambiguity Resolution, Virtual Base Class & Ctor/Dtor in Derived
Ambiguity in Inheritance
Multiple inheritance + name collisions = ambiguity. The compiler can't decide which version to call.
Type 1: Same function name in multiple bases
class A {
public:
void greet() { cout << "Hello from A\n"; }
};
class B {
public:
void greet() { cout << "Hello from B\n"; }
};
class C : public A, public B {};
int main() {
C c;
// c.greet(); // ERROR — ambiguous
c.A::greet(); // explicit qualification — "Hello from A"
c.B::greet(); // "Hello from B"
return 0;
}
Resolution: scope resolution operator :: to specify which base.
Type 2: Function overriding
When derived class defines a function with the same name as a base class function, the base function is hidden, not overridden (unless virtual — see Lesson 3).
class Base {
public:
void show() { cout << "Base::show\n"; }
};
class Derived : public Base {
public:
void show() { cout << "Derived::show\n"; } // hides Base::show
};
int main() {
Derived d;
d.show(); // Derived::show
d.Base::show(); // Base::show — explicit
return 0;
}
This is function overriding in the syllabus's terminology (technically C++ calls it hiding unless the function is virtual).
Type 3: Diamond problem
The most famous ambiguity:
class A {
public:
int x;
};
class B : public A {};
class C : public A {};
class D : public B, public C {}; // diamond!
int main() {
D d;
// d.x = 5; // ERROR — ambiguous
// D has two A subobjects (one via B, one via C)
d.B::x = 5; // explicit
d.C::x = 10;
return 0;
}
D inherits A through both B and C → D has two A subobjects, each with its own x. This is the diamond problem.
---
Virtual Base Class — solving the diamond
By making A a virtual base of B and C, D inherits only one A subobject:
class A {
public:
int x;
};
class B : virtual public A {}; // virtual inheritance
class C : virtual public A {}; // virtual inheritance
class D : public B, public C {}; // only one A — no ambiguity
int main() {
D d;
d.x = 5; // OK — only one x
return 0;
}
How virtual inheritance works
- Each virtual base appears only once in the most-derived object
- Compiler manages a hidden pointer or offset in derived objects
- Slightly slower member access (one indirection)
- Constructor of the virtual base is called by the most-derived class, not by intermediate B/C
class A {
public:
int x;
A(int v) : x(v) {}
};
class B : virtual public A {
public:
B() : A(1) {} // ignored when D constructs
};
class C : virtual public A {
public:
C() : A(2) {} // also ignored
};
class D : public B, public C {
public:
D() : A(99), B(), C() {} // D MUST initialise virtual base A
};
int main() {
D d;
cout << d.x; // 99 — D's call wins
return 0;
}
Rule: When a class has a virtual base, the most-derived class's constructor is responsible for initialising it.
---
Constructor / Destructor in Derived Classes
Construction order
- Virtual base classes (in order of declaration in the most-derived class)
- Non-virtual base classes (in order of declaration)
- Members (in order of declaration)
- Derived class constructor body
Destruction order
Reverse of construction.
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() "; }
};
int main() {
C c;
return 0;
}
// Output: A() B() C() ~C() ~B() ~A()
The base class constructor runs first (most "general" first); destructors run in reverse (most "specific" first).
Passing arguments to base constructor
If the base class has a parameterized constructor, the derived class must call it via the member initialiser list:
class Animal {
protected:
string name;
public:
Animal(string n) : name(n) {
cout << "Animal: " << name << endl;
}
};
class Dog : public Animal {
private:
string breed;
public:
Dog(string n, string b) : Animal(n), breed(b) { // call base ctor
cout << "Dog: " << breed << endl;
}
};
int main() {
Dog d("Rex", "Labrador");
// Output: Animal: Rex
// Dog: Labrador
return 0;
}
If the base has no default constructor, the derived class must explicitly call a parameterized base constructor in the initialiser list.
---
Multiple Inheritance Construction Order
When deriving from multiple bases, they construct in declaration order (the order they appear in the class header):
class A { public: A() { cout << "A "; } };
class B { public: B() { cout << "B "; } };
class C { public: C() { cout << "C "; } };
class D : public B, public A, public C { // construction order: B, A, C
public:
D() : A(), C(), B() { // these calls' order does NOT matter
cout << "D ";
}
};
D d;
// Output: B A C D
The list order in the constructor's initialiser list is for reader clarity — actual order follows declaration.
---
Member Initialiser List in Inheritance
class A {
public:
int x;
A(int a) : x(a) {}
};
class B : public A {
public:
int y;
B(int a, int b) : A(a), y(b) {} // base ctor first, then member
};
Order in the list
You can write the list in any order, but actual construction order follows:
- Bases in declaration order
- Members in declaration order
Mismatched order between list and declaration is legal but confusing. Always write list in declaration order.
---
Destructor in Inheritance — virtual destructor warning
If you delete a derived object via a base pointer, the destructor MUST be virtual in the base or you'll only call the base destructor → derived destructor never runs → memory leak.
class Base {
public:
Base() { cout << "Base()\n"; }
~Base() { cout << "~Base()\n"; } // NOT virtual — DANGER
};
class Derived : public Base {
private:
int* data;
public:
Derived() { data = new int[100]; cout << "Derived()\n"; }
~Derived() { delete[] data; cout << "~Derived()\n"; }
};
int main() {
Base* b = new Derived();
delete b; // Only ~Base() called — ~Derived() leaks!
return 0;
}
// Output: Base() Derived() ~Base()
// ↑ ~Derived() never called — LEAK
Fix: Make the base destructor virtual:
class Base {
public:
virtual ~Base() { cout << "~Base()\n"; }
};
// Now: delete b → ~Derived() → ~Base() — correct
Rule: If a class has any virtual function, it should also have a virtual destructor. Any class designed to be a base class should have a virtual destructor.
---
protected Members + Inheritance
protected exists specifically for inheritance — it's like private but visible to derived classes:
class Base {
protected:
int value; // accessible to derived
public:
Base(int v) : value(v) {}
};
class Derived : public Base {
public:
Derived(int v) : Base(v) {}
void showDouble() {
cout << value * 2; // OK — protected accessible in derived
}
};
int main() {
Derived d(10);
d.showDouble(); // 20
// d.value = 5; // ERROR — protected not accessible outside
return 0;
}
---
Hierarchical Constructor Calls Example
class Vehicle {
protected:
int wheels;
public:
Vehicle(int w) : wheels(w) {
cout << "Vehicle (" << w << " wheels) created\n";
}
~Vehicle() {
cout << "Vehicle destroyed\n";
}
};
class Car : public Vehicle {
protected:
int seats;
public:
Car(int w, int s) : Vehicle(w), seats(s) {
cout << "Car (" << s << " seats) created\n";
}
~Car() {
cout << "Car destroyed\n";
}
};
class SUV : public Car {
private:
bool fourWheelDrive;
public:
SUV(int w, int s, bool fwd) : Car(w, s), fourWheelDrive(fwd) {
cout << "SUV created\n";
}
~SUV() {
cout << "SUV destroyed\n";
}
};
int main() {
SUV s(4, 7, true);
return 0;
}
// Output:
// Vehicle (4 wheels) created
// Car (7 seats) created
// SUV created
// SUV destroyed
// Car destroyed
// Vehicle destroyed
---
Virtual Base + Constructor — Complete Example
class Person {
protected:
string name;
public:
Person(string n) : name(n) {
cout << "Person: " << name << endl;
}
};
class Student : virtual public Person {
public:
Student(string n) : Person(n) {
cout << "Student\n";
}
};
class Employee : virtual public Person {
public:
Employee(string n) : Person(n) {
cout << "Employee\n";
}
};
class TA : public Student, public Employee {
public:
// Most-derived class is responsible for virtual base
TA(string n) : Person(n), Student(n), Employee(n) {
cout << "TA\n";
}
};
int main() {
TA t("Rohit");
return 0;
}
// Output:
// Person: Rohit
// Student
// Employee
// TA
Without virtual public Person, the TA object would have two Person subobjects — one via Student, one via Employee — and ambiguity for any access to name.
---
Key Terms — Lesson 3.2
The terms below cover ambiguity resolution, virtual base classes, and constructor/destructor behaviour in inheritance — every PYQ on these topics expects fluent use.
Ambiguity (Multiple Inheritance) — A situation where a name is inherited from more than one base class and the compiler cannot decide which one the user means. class C : public A, public B {}; and both A and B define void f() — the call obj.f() is ambiguous.
Scope Resolution for Ambiguity — Disambiguating an inherited name by qualifying it with the base-class name: obj.A::f() or obj.B::f(). The standard C++ technique for resolving inherited-name ambiguity.
Method Hiding (Name Hiding) — When a derived class defines a member with the same name as a base member, the derived name hides the base even if signatures differ. Accessing the hidden base member requires explicit scope (Base::method()) or using Base::method; to bring it back.
Diamond Problem — A specific ambiguity in multiple inheritance: when two base classes both inherit from a common grandparent (A → B, A → C, B+C → D), class D has two copies of A's members, causing ambiguity for any A-inherited access. The classical hazard of unrestricted multiple inheritance.
Diamond Hierarchy — The 4-class inheritance shape that produces the diamond problem: one common base (Person), two intermediate classes (Student, Employee), and one derived (TeachingAssistant). Without intervention, the derived class has two Person subobjects.
Virtual Base Class — A base class declared with the virtual keyword in the derivation list (class Student : virtual public Person). Tells the compiler: all paths to this base must share a single subobject. Solves the diamond problem by ensuring only one Person subobject exists in the most-derived class.
Most-Derived Class — In a virtual-inheritance hierarchy, the bottom-most class in the chain. The most-derived class is responsible for initialising the virtual base in its constructor's initialiser list; intermediate classes' virtual-base initialisers are ignored.
Constructor Order (Inheritance) — When a derived object is constructed: (1) virtual base classes constructed first (in order of declaration), (2) non-virtual base classes (in left-to-right declaration order), (3) member objects (in declaration order in the class), (4) derived constructor body executes.
Destructor Order (Inheritance) — When a derived object is destroyed: exactly the reverse of construction. Derived destructor body executes first, then member destructors (reverse declaration order), then non-virtual base destructors (right-to-left), then virtual base destructors. Reverse order guarantees that each subobject is alive when its containing object is being destroyed.
Calling Virtual Functions in Constructors — A dangerous pattern: during base-class constructor execution, the object's dynamic type is still base, not derived. Virtual calls dispatch to the base version, not the derived override. This is the opposite of the runtime behaviour after construction completes and is a frequent source of subtle bugs.
override Specifier (C++11) — A keyword used in derived classes to mark a function as overriding a base virtual function: void f() override;. The compiler verifies the function actually overrides something — if there's no matching virtual function in the base, compilation fails. Catches typos, signature mismatches, and silent hiding bugs.
final Specifier (C++11) — A keyword that prevents further inheritance or override. class Sealed final cannot be inherited from. void f() override final cannot be overridden in classes that further derive. Useful for sealing parts of a hierarchy.
using Declaration (in Class Scope) — A way to bring a base-class name into the derived class's scope, so it is not hidden by a same-named derived member: using Base::method;. Useful for restoring access after a derived class declares an overload with the same name.
Multiple Inheritance (recap) — A derived class inheriting from more than one base class. C++ supports it; Java/C# do not. Useful for genuine multi-role classes (a TA is both a Student and an Employee) but introduces ambiguity issues when bases share members or grandparents.
Constructor Initialiser List (Inheritance) — The mechanism by which a derived constructor invokes a specific base-class constructor: Derived(int x, int y) : Base(x), member(y) {}. Without specification, the base's default constructor is called.
Sub-Object — A portion of a derived object representing its base-class part. A Car object contains a Vehicle sub-object plus Car-specific members. With non-virtual diamond inheritance, the derived object contains two grandparent sub-objects; with virtual inheritance, one shared.
Virtual Inheritance Cost — Virtual base classes have runtime overhead — the derived object stores extra pointer(s) to locate the shared base sub-object, and access to virtual-base members goes through an indirection. This is the price of solving the diamond problem.
MRO (Method Resolution Order) — Python's algorithm (C3 linearisation) for resolving method lookup in multiple-inheritance hierarchies. C++ doesn't have MRO as such — it uses the explicit base-listing order — but the term appears in cross-language discussions.
Mix-In Class — A class designed to be inherited from for a specific capability, rather than to model an is-a relationship. Mix-ins typically contain only methods, no data. Common in template-heavy C++ (CRTP) and in dynamic languages.
CRTP (Curiously Recurring Template Pattern) — An advanced C++ idiom where a class inherits from a template specialised on itself: class Derived : public Base<Derived>. Enables static (compile-time) polymorphism without virtual functions. Used heavily in modern C++ libraries (Eigen, std::enable_shared_from_this).
---
Study deep
- Diamond problem occurs only in C++ (among major languages). Java forbids multiple class inheritance, so no diamond. C# similar. Python uses MRO (Method Resolution Order). C++'s solution — virtual inheritance — is powerful but adds runtime cost.
- Construction order matters semantically. A derived class can use base class state in its constructor body (since base is fully constructed first). Cannot use derived state in base's constructor (derived is not yet constructed).
- Calling virtual functions in constructors is dangerous. During base constructor execution, the object is still a Base, not yet a Derived. Virtual calls dispatch to Base's version, not Derived's — often unexpected.
overridekeyword (C++11). When overriding a virtual function, writeoverride:
class Derived : public Base {
public:
void f() override; // compiler checks Base has virtual f()
};
Catches typos and signature mismatches.
finalkeyword (C++11). Prevent further overriding/inheritance:
class NoMoreSubclasses final { /* ... */ }; // can't inherit
class Derived : Base {
void f() override final; // can't override f() further
};
PYQ pattern (very common): "Explain the diamond problem. How is it solved using virtual base class?" — Diagram of diamond; show ambiguity; show virtual inheritance; explain that most-derived class initialises virtual base.
PYQ pattern: "Explain order of constructor and destructor calls in inheritance with example." — Code with 3-level hierarchy; show output (A B C ~C ~B ~A); state rules (bases first, then members, derived body; reverse for destructor).
PYQ pattern: "What is function overriding? Differentiate it from function overloading." — Overloading: same name, different signatures, same scope; overriding: same signature, different scope (derived hides/redefines base); only overriding involves inheritance.