Lesson 5: Advanced Inheritance, Static Members & Friendships
Lesson Overview
This lesson deals with advanced structures in C++ object relationships and class-level capabilities. We will investigate how to inherit from multiple parent classes, how to handle the infamous Diamond Problem using virtual base classes, how static members belong to the class itself rather than to individual instances, and how the
friendkeyword allows external functions or classes to bypass encapsulation rules securely.
π 1. The βWhyβ & Concept Breakdown
A. Multiple Inheritance & Ambiguity
In multiple inheritance {a single child deriving from two or more parents}, a derived class inherits directly from two or more base classes.
- The Trap: If both parent classes contain a member function with the exact same name (e.g.,
display()), callingchildObj.display()triggers a compile-time ambiguity error {compiler cannot choose between two identical parent functions}. - The Resolution: Qualify the call using the scope resolution operator (
childObj.ParentA::display()) or override it inside the derived class.
B. The Diamond Problem & Virtual Base Classes
The Diamond Problem occurs when a child class inherits from two parent classes that share a common grandparent base class:
graph TD A["[Grandparent Class: Person]<br>(name)"] B["[Parent 1: Student]<br>(roll_number)"] C["[Parent 2: Sports]<br>(athletic_score)"] D["[Child Class: Result]<br>(gpa)"] A -->|virtual public| B A -->|virtual public| C B --> D C --> D
- The Issue Without
virtual: The child class receives two separate copies of the grandparentβs variables (namethroughStudentandnamethroughSports), causing memory duplication and syntax ambiguity. - The Solution With
virtual: Marking the intermediate inheritance asvirtual public Person{virtual base class} ensures the compiler creates and maintains only one shared instance ofPersoninside the child object. - Grandparent Constructor Responsibility: In virtual base inheritance, the intermediate classesβ constructor calls to the grandparent are bypassed; the most-derived child constructor is directly responsible for initializing the virtual grandparent!
β‘ Advanced Inheritance & Scope: Python vs. C++
- Diamond Resolution: Python uses automatic MRO linearization (
__mro__). C++ requires explicitvirtual public Baseto avoid duplicate grandparent subobjects in memory.- Static Members:
static int count;inside a class only declares the symbol. You must define and allocate it globally outside the class (int Class::count = 0;).- Friend Functions: Grants non-member global functions access to private variables without creating class instances or passing
this.
C. Static Class Members
- Static Data Members: Exist in the static/global memory segment. Only one copy exists, shared across all instances of the class. They must be declared inside the class and defined outside the class at global scope to allocate physical memory {class-level shared variable}.
- Static Member Functions: Belong to the class blueprint. Can be called without an object (
ClassName::func()). Because they are not bound to an object, they do not have athispointer {hidden instance pointer} and cannot access non-static member variables.
D. Friend Functions & Classes
- Friend Function: A non-member global function declared inside a class with the
friendkeyword {special trust pass}, granting it access to that classβsprivateandprotectedmembers.
D. Friend Functions & Friend Classes
- Friend Function: A non-member global function granted private/protected access to a class. It has no
thispointer, is declared withfriendinside the class, and defined globally without thefriendkeyword and withoutClassName::. - Forward Declaration: If a friend function takes parameters from two different classes, declare the second class upfront (
class ClassB;) so the compiler recognizes its name.
π 2. Keyword & Syntax Dictionary
| Keyword / Syntax | Purpose | Example |
|---|---|---|
virtual public | Declares a parent as a virtual base class, resolving diamond duplication. | class Student : virtual public Person { ... }; |
static | Declares a shared class-level variable or static member function. | static int count; |
friend | Grants an outside function or class full private access. | friend void compare(Account a, Account b); |
ClassName::var | Defines and allocates memory for static class variables globally. | int Counter::count = 0; |
π» 3. Step-by-Step Code Evolution
Step 5.1: The Diamond Problem Solved via Virtual Base Class
#include <iostream>
#include <string>
using namespace std;
class Person {
protected:
string name;
public:
Person(string n) : name(n) {
cout << "[+] Grandparent Person Constructor for: " << name << endl;
}
};
// Parent 1 inherits virtually
class Student : virtual public Person {
protected:
int roll_number;
public:
Student(string n, int r) : Person(n), roll_number(r) {
cout << "[+] Parent Student Constructor" << endl;
}
};
// Parent 2 inherits virtually
class Sports : virtual public Person {
protected:
float athletic_score;
public:
Sports(string n, float s) : Person(n), athletic_score(s) {
cout << "[+] Parent Sports Constructor" << endl;
}
};
// Child inherits from both Parents
class Result : public Student, public Sports {
float gpa;
public:
// Most-derived class directly initializes virtual grandparent Person!
Result(string n, int r, float s, float g)
: Person(n), Student(n, r), Sports(n, s), gpa(g) {
cout << "[+] Child Result Constructor" << endl;
}
void display() const {
cout << "Name: " << name << " | Roll: " << roll_number
<< " | Score: " << athletic_score << " | GPA: " << gpa << endl;
}
};
int main() {
Result res("Alice", 101, 88.5f, 3.92f);
res.display();
return 0;
}Output Log:
[+] Grandparent Person Constructor for: Alice
[+] Parent Student Constructor
[+] Parent Sports Constructor
[+] Child Result Constructor
Name: Alice | Roll: 101 | Score: 88.5 | GPA: 3.92Step 5.2: Static Members (Counting Active Instances)
#include <iostream>
using namespace std;
class Counter {
private:
int id;
static int total_objects; // Declared inside class
public:
Counter() {
total_objects++;
id = total_objects;
}
void displayID() const { cout << "Object ID: " << id << endl; }
// Static member function
static int getTotal() {
return total_objects; // Cannot access 'id' here (no 'this' pointer)
}
};
// Defined and initialized at global scope (REQUIRED)
int Counter::total_objects = 0;
int main() {
Counter c1, c2, c3;
cout << "Total active objects: " << Counter::getTotal() << endl; // Outputs: 3
return 0;
}Step 5.3: Friend Function Bridging Two Separate Classes
#include <iostream>
#include <string>
using namespace std;
class ScholarshipOffice; // Forward Declaration
class StudentRecord {
private:
string name;
float gpa;
public:
StudentRecord(string n, float g) : name(n), gpa(g) {}
friend void checkEligibility(const StudentRecord& s, const ScholarshipOffice& office);
};
class ScholarshipOffice {
private:
string title;
float minGpa;
public:
ScholarshipOffice(string t, float m) : title(t), minGpa(m) {}
friend void checkEligibility(const StudentRecord& s, const ScholarshipOffice& office);
};
// Defined globally without 'friend' or class scope
void checkEligibility(const StudentRecord& s, const ScholarshipOffice& office) {
if (s.gpa >= office.minGpa) {
cout << "[APPROVED] " << s.name << " qualified for " << office.title << endl;
} else {
cout << "[REJECTED] " << s.name << " does not meet " << office.minGpa << " cutoff." << endl;
}
}
int main() {
StudentRecord st("Charlie", 3.85f);
ScholarshipOffice deanAward("Dean's Merit Scholarship", 3.75f);
checkEligibility(st, deanAward);
return 0;
}π§ 4. Under-the-Hood Memory Visualizations
Memory Layout: Diamond Inheritance (Non-Virtual vs. Virtual)
Without virtual (Duplicated Memory):
ββββββββββββββββββββββββββββββββββββββββ
β Class 'Result' Object Space β
β - [Student Subobject: name, roll] β
β - [Sports Subobject: name, score] β <-- Duplicate 'name' on Stack!
β - gpa β
ββββββββββββββββββββββββββββββββββββββββ
With virtual public Person (Single Shared Base):
ββββββββββββββββββββββββββββββββββββββββ
β Class 'Result' Object Space β
β - [Student Subobject: vptr, roll] β
β - [Sports Subobject: vptr, score] β
β - gpa β
β - [Shared Person Subobject: name] β <-- Single unified copy of 'name'
ββββββββββββββββββββββββββββββββββββββββ
β οΈ 5. The Debuggerβs Guide (Common Traps)
Trap 1: Grandparent Missing in Child Initializer List
In virtual inheritance, if the grandparent class has no default constructor, the child constructor must explicitly call the grandparent constructor (
Result(...) : Person(n), ...). Forgetting this causes compilation failure.
Trap 2: Undefined Reference to Static Member
Declaring
static int count;inside a class only creates a blueprint. You must define it outside the class in global scope:int MyClass::count = 0;.
Trap 3: Accessing Non-Static Members in Static Functions
Static member functions have no
thispointer and cannot reference instance variables.
βοΈ 6. KUET Lab Test 01 (ECE 2k23) Friend Function Solutions
Solution A: Same-Class Friend Comparison (Account compareBalance)
(Sourced directly from KUET Lab Test 01 - ECE 2k23 Bank Account Paper)
#include <iostream>
#include <string>
using namespace std;
class Account {
private:
int depositorID;
float balance;
public:
long acc_no;
char type;
string depositorName;
void assignInitialValues(int id, string name, long acc, char t, float bal) {
depositorID = id;
depositorName = name;
acc_no = acc;
type = t;
balance = bal;
}
void display() const {
cout << "Name: " << depositorName
<< " | Acc No: " << acc_no
<< " | Type: " << type
<< " | Balance: $" << balance << endl;
}
// Friend Function Declaration (Returns entire Account object)
friend Account compareBalance(Account a1, Account a2);
};
// Global definition
Account compareBalance(Account a1, Account a2) {
return (a1.balance >= a2.balance) ? a1 : a2;
}
int main() {
Account accs[2];
accs[0].assignInitialValues(101, "Alice", 112233, 'S', 5000.50f);
accs[1].assignInitialValues(102, "Bob", 445566, 'C', 12000.75f);
cout << "=== Comparing Accounts ===" << endl;
Account richer = compareBalance(accs[0], accs[1]);
cout << "Higher balance belongs to: " << richer.depositorName << endl;
return 0;
}Solution B: Same-Class Friend Comparison (Book compareAvailability)
(Sourced directly from KUET Lab Test 01 - ECE 2k23 Library Book Paper)
#include <iostream>
#include <string>
using namespace std;
class Book {
private:
int bookID;
char availability; // 'A' = Available, 'I' = Issued
public:
string bookTitle;
string authorName;
void assignInitialValues(int id, string title, string author, char status) {
bookID = id;
bookTitle = title;
authorName = author;
availability = status;
}
void displayBookDetails() const {
cout << "Title: " << bookTitle
<< " | Status: " << (availability == 'A' ? "Available" : "Issued") << endl;
}
friend Book compareAvailability(Book b1, Book b2);
};
Book compareAvailability(Book b1, Book b2) {
if (b1.availability == 'A') return b1;
if (b2.availability == 'A') return b2;
return b1; // Default fallback
}
int main() {
Book b1, b2;
b1.assignInitialValues(1, "Modern C++", "Stroustrup", 'I');
b2.assignInitialValues(2, "OOP with C++", "Balagurusamy", 'A');
Book available = compareAvailability(b1, b2);
cout << "Available Book: " << available.bookTitle << endl;
return 0;
}π 7. Practice Quiz
- In private inheritance, public members of the base class become __________ in the derived class. (Answer: private)
- The Diamond Problem is solved using a __________ base class. (Answer: virtual)
- The
thispointer is not available inside __________ member functions or __________ functions. (Answer: static; friend) - Static member variables are stored in the:
- A. Stack segment
- B. Global / Static data segment
- C. Heap segment
- D. Code segment (Answer: B)
- What keyword grants a non-member function access to private data members? (Answer: friend)
π¬ 8. Viva Quick-Prep
Q1: Can a static member function access a non-static member variable? Why or why not?
Answer: No. Static member functions belong to the class as a whole and do not receive an implicit this pointer. Without an active object reference, they cannot determine which instanceβs variable to access.
Q2: Why are static member variables defined outside the class?
Answer: Class declarations are blueprints that do not allocate storage. Static variables exist independently of object instantiations and must be defined globally to allocate memory in the static data segment before execution begins.
Q3: Why is a friend function defined without ClassName::?
Answer: Because a friend function is not a member function of the class; it is a global function with special access permissions.
Q4: How does a virtual base class solve the Diamond Problem?
Answer: By marking intermediate base classes as virtual public, the compiler ensures only one shared copy of the grandparent class is created in the derived object, resolving memory duplication and member ambiguity.