Chapter 3 — Object-Oriented Programming with C++

INT1339 — C++ Programming Language

Classes

Blueprint · Attributes · Methods

Encapsulation

Private · Protected · Public

Inheritance

Base · Derived · Hierarchy

Polymorphism

Virtual · Override · vtable

Abstraction

Pure Virtual · Interfaces

Chapter 3 Contents

Part I

Classes — Coursebook Chapter 5

01

Concept of Object Classes

Definition, instantiation, and object usage

02

Class Components

Attributes (data members) and methods (member functions)

03

Class Access Scope

Access modifiers, friend functions & friend classes

04

Constructors and Destructors

Initialization, overloading, and object lifecycle

05

Object Pointers & Arrays

Dynamic allocation, arrow operator, collections

Part II

Inheritance & Polymorphism — Chapter 6

01

Concept of Inheritance

Base & derived classes, derivation modes

02

Constructors & Destructors in Inheritance

Execution order and initialization lists

03

Accessing Members in Inheritance

Protected access, overriding, upcasting

04

Multiple Inheritance

Multiple bases, ambiguity, Diamond Problem

05

Abstract Base Classes

Pure virtual functions and C++ interface pattern

06

Polymorphism & Dynamic Binding

Virtual functions, override, and vtable mechanism

After This Chapter, Students Will Be Able To:

1

Master Class Architecture

Define classes with encapsulated data (private), controlled interfaces (public), and constructor initializer lists.

2

Control Scope & Friendship

Correctly apply access modifiers and declare friend functions/classes when justified.

3

Manage Object Memory

Allocate and deallocate objects and arrays dynamically using new, delete, and the arrow operator.

4

Build Inheritance Hierarchies

Implement single, multi-level, and multiple inheritance while preventing the Diamond Problem.

5

Implement Dynamic Polymorphism

Use virtual functions and base class pointers for runtime dynamic binding with vtable dispatch.

6

Design with Abstract Classes

Formulate pure virtual contracts (= 0) and build modular systems like MiniPOS V3.0.

From Structs to Classes: The OOP Paradigm Shift

Chapter 2: Plain Data Struct

struct BankAccount {
  string owner;
  double balance;
};
BankAccount acc = {"Alice", 1000000};
acc.balance = -999999; // Anyone can corrupt!

The OOP Solution: The Class

An object encapsulates state (attributes) and behavior (methods) together. Internal data is hidden behind private access. Changes are only permitted through verified public member functions.

Hidden State

Private fields

Safe Interface

Public methods

Validated Writes

Business logic

Part I · Classes

Concept of Object Classes

Definition of Classes · Instantiation of Objects · Blueprint vs Instance

Part I · Classes

Defining Object Classes

Class Syntax · Member Declarations · Semicolon Rule

Class vs Object: The Blueprint & The House

Class — The Blueprint

A Class is a user-defined type — a template that defines what attributes and methods all objects of this type will have. It allocates no memory for data on its own.

Object — The Instance

An Object is a concrete instance of a class, occupying real memory at runtime. Multiple objects can be created from the same class, each holding its own independent state.

Class Syntax and Structure

class ClassName {
private:
  // Private attributes (hidden from outside)
  DataType attribute1;
  DataType attribute2;

public:
  // Public methods (accessible from outside)
  void setAttribute1(DataType val);
  DataType getAttribute1() const;
  void executeAction();

}; // Note: semicolon is MANDATORY!

Critical Rules

1

PascalCase Identifier

Use the class keyword followed by the class name in PascalCase.

2

Curly Brace Body

Enclose all member declarations in { ... }.

3

Mandatory Semicolon

The closing brace must end with ;. Omitting it causes compiler errors.

4

Default: private

Unlike struct (defaults to public), a class defaults all members to private.

Part I · Classes

Using Object Classes

Instantiating Objects · The Dot Operator · Stack vs Heap Instantiation

Creating Objects and Accessing Members

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

class Product {
private:
  string name;
  double price;
public:
  void init(string n, double p) {
    name = n;
    price = (p >= 0) ? p : 0;
  }
  void display() const {
    cout << name << " - " << price << " VND" << endl;
  }
};

int main() {
  Product item1;
  item1.init("Mechanical Keyboard", 1200000);
  item1.display(); // dot operator
  // item1.price = -500; // COMPILE ERROR!
  return 0;
}

Key Mechanics

Dot Operator (.)

Accesses public methods and variables on an object instance: item1.display()

Encapsulation Enforced

The compiler blocks any external access to private members. Invalid values cannot be forced into price.

Part I · Classes

Class Components: Attributes & Methods

Data Representation (State) · Functional Operations (Behavior)

Part I · Classes

Class Attributes

Storing State · Types of Attributes · Memory Footprint per Instance

Attributes: Representing Object State

Data Members in Code

class Student {
private:
  string studentId;    // e.g., "B23DCCN001"
  string fullName;     // Student full name
  double midtermScore; // Scale 0.0 - 10.0
  double finalScore;   // Scale 0.0 - 10.0
};

Attributes (member variables / fields) hold the data that defines the state of an object. Each instance of Student gets its own unique memory block for these attributes.

Memory Independence

student1

studentId: "B23DCCN001"
fullName: "Nguyen Van An"
midtermScore: 8.5
finalScore: 7.5

student2

studentId: "B23DCCN002"
fullName: "Le Thi Binh"
midtermScore: 9.0
finalScore: 9.5

Part I · Classes

Class Methods

In-Class Definitions · Scope Resolution (::) · The const Method Qualifier

Member Functions: Inside vs Outside the Class

class Circle {
private:
  double radius;
public:
  void setRadius(double r); // Declaration only

  // Defined inside: treated as inline
  double getRadius() const { return radius; }

  double calcArea() const; // Declaration only
};

// Definition OUTSIDE using Scope Resolution (::)
void Circle::setRadius(double r) {
  radius = (r > 0) ? r : 0.1;
}

double Circle::calcArea() const {
  return 3.14159 * radius * radius;
}

Best Practices

Declarations Inside

Place clean declarations inside the class (or .h header file) for a compact interface.

Definitions Outside

Implement methods using ClassName::MethodName in a .cpp source file.

const for Read-Only

Mark methods that only read data with const to guarantee const-correctness and allow calls on const objects.

Checkpoint: Class Definition & Components

Question 1

What happens if you define a class in C++ without any access modifier specified for its members?

  • A. All members default to public
  • B. All members default to protected
  • C. All members default to private
  • D. The code generates a compile error

Question 2

Why should read-only member functions (like calcArea()) be qualified with const?

  • A. To make them run faster by caching results
  • B. To guarantee they cannot modify any attribute and allow calls on const objects
  • C. To make the function accessible without an object
  • D. To allow the function to return multiple values

Question 3

What is the operator used to define a class method outside of the class body?

  • A. The arrow operator (->)
  • B. The dot operator (.)
  • C. The scope resolution operator (::)
  • D. The address-of operator (&)

Checkpoint: Class Definition & Components — Answers

Q1: C — All members default to private ✓

In C++, the default access level for class is private. In contrast, struct defaults to public. This is the fundamental syntactic distinction between class and struct in C++.

Q2: B — Guarantees no modification, allows const calls ✓

Appending const promises the method will not alter *this. If an object is created as const Circle c(5.0);, only const member functions can be called on it.

Q3: C — Scope Resolution Operator (::) ✓

The syntax ReturnType ClassName::MethodName(params) tells the compiler that MethodName belongs to the scope of ClassName. The :: operator resolves namespace membership.

Part I · Classes

Class Access Scope & Friends

Access Modifiers · Encapsulation Safeguards · Friend Functions & Classes

Part I · Classes

Access Scope

Private · Protected · Public — Controlling Data Visibility · Defense-in-Depth for Object State

Access Modifiers: The Pillars of Encapsulation

class VaultAccount {
private:
  double secretBalance; // ONLY within VaultAccount
protected:
  string accountTier;   // VaultAccount + child classes
public:
  string ownerName;     // Accessible by any code
};

Getters, Setters, and Business Logic Validation

class BankAccount {
private:
  string accountNumber;
  double balance;
public:
  BankAccount(string id, double init) {
    accountNumber = id;
    balance = (init >= 0) ? init : 0;
  }
  // Getter: Read-only access
  double getBalance() const { return balance; }

  // Setter with business rule enforcement
  bool deposit(double amount) {
    if (amount <= 0) return false;
    balance += amount;
    return true;
  }
  bool withdraw(double amount) {
    if (amount <= 0 || amount > balance) return false;
    balance -= amount;
    return true;
  }
};

Why This Matters

No Direct Mutation

acc.balance = -1000 is impossible — field is private.

Invariant Enforced

Withdrawals cannot exceed the existing balance. The class owns its rules.

Single Point of Control

All mutations pass through validated setters, making bugs easy to trace.

Part I · Classes

Friend Functions

Controlled Encapsulation Exceptions · The friend Keyword

Friend Classes: Tightly Coupled Subsystems

class Transaction;

class Account {
private:
  double balance;
  string pinCode;
  friend class Transaction; // Grant full access
public:
  Account(double b, string pin)
    : balance(b), pinCode(pin) {}
};

class Transaction {
public:
  bool transfer(Account& from, Account& to,
                double amount, string pin) {
    if (from.pinCode != pin) return false; // private!
    if (from.balance < amount) return false;
    from.balance -= amount; // private!
    to.balance += amount;
    return true;
  }
};

Design Rules for Friendship

Not Reciprocal

Account cannot access Transaction's private data just because Transaction is Account's friend.

Not Inherited

Child classes of Transaction do NOT inherit the friendship privilege.

Use Sparingly

Friendship is a deliberate coupling. Over-use defeats encapsulation and makes code harder to maintain.

Part I · Classes

Constructors and Destructors

Object Initialization · Member Initializer Lists · Destructor Cleanups

Part I · Classes

Constructors

Automatic Lifecycle Initialization · Default & Parameterized Constructors

Constructors: Safe Object Birth

class Product {
private:
  string name;
  double price;
  int stock;
public:
  // 1. Default Constructor
  Product()
    : name("Unnamed"), price(0.0), stock(0) {}

  // 2. Parameterized with Member Initializer List
  Product(string n, double p, int s)
    : name(n),
      price(p >= 0 ? p : 0.0),
      stock(s >= 0 ? s : 0) {
    // Body for secondary setup
  }
};

Constructor Essentials

Same Name as Class

The constructor identifier must match the class name exactly.

No Return Type

Not even void — the compiler handles this automatically.

Initializer Lists

Initializes members directly during memory allocation — avoids redundant default-construction + re-assignment. Required for const and reference members.

Constructor Overloading: Multiple Ways to Instantiate

class Order {
private:
  int orderId;
  string customerName;
  double totalAmount;
public:
  // Overload 1: Empty order
  Order() : orderId(0), customerName("Guest"), totalAmount(0.0) {}
  // Overload 2: Standard order
  Order(int id, string name) : orderId(id), customerName(name), totalAmount(0.0) {}
  // Overload 3: Completed order with amount
  Order(int id, string name, double total) : orderId(id), customerName(name), totalAmount(total) {}
};

int main() {
  Order o1;                              // Calls Overload 1
  Order o2(101, "Nguyen Van A");         // Calls Overload 2
  Order o3(102, "Tran Thi B", 450000.0); // Calls Overload 3
  return 0;
}

Overload 1

No arguments — default Guest order

Overload 2

ID + name — open order, no total yet

Overload 3

ID + name + total — complete record

Part I · Classes

Destructors

Object Deallocation · Resource Release · Automatic Cleanup Order

Destructors: Deterministic Resource Reclamation

class DynamicArray {
private:
  int* data;
  int size;
public:
  DynamicArray(int s) : size(s) {
    data = new int[size]; // Acquire resource
    cout << "Allocated array of size " << size << endl;
  }

  ~DynamicArray() {
    delete[] data; // Release deterministically!
    cout << "Deallocated array of size " << size << endl;
  }
};

Destructor Rules

Tilde Prefix

Named ~ClassName() — automatically recognized by the compiler.

No Parameters

Takes no arguments, has no return type. A class can have only ONE destructor.

RAII Principle

C++ guarantees destructors execute even when functions exit early or throw exceptions — preventing memory and file handle leaks.

Checkpoint: Access Scope & Lifecycle

Question 1

If class Beta is declared as a friend of class Alpha, which is true?

  • A. Alpha can access private members of Beta
  • B. Beta can access private members of Alpha
  • C. Both classes can access each other's private members
  • D. Children of Beta inherit access to Alpha

Question 2

When stack objects go out of scope, in what order do their destructors run?

  • A. First created, first destroyed (FIFO)
  • B. Last created, first destroyed (LIFO / Reverse order)
  • C. In alphabetical order of variable names
  • D. Destructors only run if called manually

Checkpoint: Access Scope & Lifecycle — Answers

Q1: B — Beta can access Alpha's private members ✓

Friendship in C++ is unidirectional and not reciprocal. Declaring friend class Beta; inside Alpha gives Beta keys to Alpha's secrets, but Alpha does not gain access to Beta.

Q2: B — Last created, first destroyed (LIFO) ✓

Stack frames unwind in Last-In-First-Out order. The object constructed last is destroyed first, matching the behavior of the call stack memory model.

Part I · Classes

Object Pointers & Object Arrays

Heap Allocation (new/delete) · Pointer Access (->) · Array of Instances

Part I · Classes

Object Pointers

Pointers to Classes · Heap Instantiation · Arrow Operator (->)

Object Pointers and Heap Allocation

class Product {
private:
  string name;
  double price;
public:
  Product(string n, double p) : name(n), price(p) {}
  void display() const {
    cout << name << ": " << price << " VND" << endl;
  }
};

int main() {
  // 1. Pointer to STACK object
  Product p1("Mouse", 250000);
  Product* ptr1 = &p1;
  ptr1->display(); // Arrow operator

  // 2. Dynamic HEAP object
  Product* ptr2 = new Product("Monitor 27-inch", 4500000);
  ptr2->display();

  delete ptr2;  // Mandatory cleanup!
  ptr2 = nullptr;
  return 0;
}

Arrow Operator (->)

The arrow operator is syntactic sugar for dereferencing followed by member access:

Stack Pointer

Points to stack memory. Memory freed automatically when scope ends.

Heap Pointer

Points to heap memory. Must call delete explicitly to avoid memory leaks.

Part I · Classes

Object Arrays

Fixed Arrays of Instances · Initialization · Iterating Collections

Object Arrays: Collections of Entities

class Employee {
private:
  int id;
  string name;
public:
  Employee() : id(0), name("N/A") {} // REQUIRED!
  Employee(int i, string n) : id(i), name(n) {}
  void print() const {
    cout << "[" << id << "] " << name << endl;
  }
};

int main() {
  Employee staff[3] = {
    Employee(101, "Nguyen Van A"),
    Employee(102, "Tran Thi B"),
    Employee(103, "Le Van C")
  };

  for (int i = 0; i < 3; i++) {
    staff[i].print(); // dot operator with index
  }
  return 0;
}

Memory Layout

staff[0]

ID: 101, Name: "Nguyen Van A"

staff[1]

ID: 102, Name: "Tran Thi B"

staff[2]

ID: 103, Name: "Le Van C"

Part II · Inheritance & Polymorphism

Concept of Inheritance

The "Is-A" Relationship · Base & Derived Classes · Code Reuse Without Duplication

Part II · Inheritance

Declaring Inheritance

Derivation Syntax · Establishing Parent-Child Hierarchies

Inheritance: Modeling Hierarchical Relationships

// Base Class (Parent)
class Employee {
protected:
  string name;
  double baseSalary;
public:
  Employee(string n, double s)
    : name(n), baseSalary(s) {}
  void printBase() const {
    cout << name << " | Base: " << baseSalary;
  }
};

// Derived Class: "Developer IS AN Employee"
class Developer : public Employee {
private:
  string techStack;
public:
  Developer(string n, double s, string tech)
    : Employee(n, s), techStack(tech) {}
  void printDev() const {
    printBase();
    cout << " | Tech: " << techStack << endl;
  }
};

What Inheritance Gives Us

Code Reuse

Developer automatically has name and baseSalary — no duplication needed.

Is-A Relationship

A Developer IS AN Employee. Anywhere an Employee is expected, a Developer can be used.

Extension

Developer adds techStack on top of the base — specialization without modification.

Part II · Inheritance

Derivation Properties

Public, Protected, and Private Inheritance Modes

Public, Protected, and Private Inheritance Modes

Use public 99% of the time

Represents true subtyping ("Is-A"). An instance of Developer can substitute for an Employee wherever needed.

⚠️ private base members

Private members of the base class are never directly accessible in the child, regardless of which derivation mode is chosen.

Part II · Inheritance

Constructors & Destructors in Inheritance

Constructor Chaining · Base Initialization Order · Reverse Destruction Order

Part II · Inheritance

Constructors in Inheritance

Explicit Base Construction · Initialization Sequencing

Constructor Chaining: Base-First Rule

class Base {
public:
  Base() {
    cout << "1. Base Constructor" << endl;
  }
  Base(int x) {
    cout << "1. Base Parameterized: " << x << endl;
  }
};

class Derived : public Base {
public:
  // Explicitly pass parameter up to Base
  Derived(int x, int y) : Base(x) {
    cout << "2. Derived Constructor: " << y << endl;
  }
};

int main() {
  Derived obj(10, 20);
  return 0;
}

The Base-First Rule

Step 1: Base Runs

The base class constructor runs first to establish the foundation — initializing inherited members.

Step 2: Derived Runs

Once the parent foundation is ready, the derived constructor executes its body to add specialized behavior.

Part II · Inheritance

Destructors in Inheritance

LIFO Destruction · Cleaning Child Resources First

Destructors in Inheritance: Derived-First Rule

class Base {
public:
  ~Base() {
    cout << "2. Base Destructor" << endl;
  }
};

class Derived : public Base {
public:
  ~Derived() {
    cout << "1. Derived Destructor" << endl;
  }
};

int main() {
  {
    Derived d;
  } // Scope ends here — both destructors fire
  return 0;
}

Why Reverse Order?

Base Constructed First

Foundation is built before specialized features.

Derived Constructed Last

Child adds features on top of the ready base.

Derived Destroyed First

Child may depend on base resources while cleaning up — must release its own first.

Base Destroyed Last

Foundation is dismantled only after all dependents are done.

Part II · Inheritance

Accessing Inherited Members

Protected Members · Method Overriding · Upcasting Type Conversions

Part II · Inheritance

Using Base Class Members

The protected Specifier · Resolving Hidden Scope with Base::

Protected Members and Scope Resolution

class Account {
protected:
  double balance; // accessible to child classes
public:
  Account(double b) : balance(b) {}
  void printStatement() const {
    cout << "Balance: " << balance << endl;
  }
};

class SavingsAccount : public Account {
private:
  double interestRate;
public:
  SavingsAccount(double b, double r)
    : Account(b), interestRate(r) {}

  void applyMonthlyInterest() {
    balance += balance * interestRate; // OK! protected
  }

  void printStatement() const {
    Account::printStatement(); // Explicit base call
    cout << "Interest Rate: "
         << interestRate * 100 << "%" << endl;
  }
};

Key Concepts

protected Role

Bridges complete encapsulation (private) and full exposure (public). Accessible to the class and its children — not to outsiders.

Scope Resolution (Base::)

Use Account::printStatement() to explicitly call the base version of an overridden method from within the child.

Part II · Inheritance

Overriding Base Class Methods

Redefining Behaviors · Static Name Hiding

Method Overriding in Derived Classes

class Printer {
public:
  void print() const {
    cout << "Standard Black-and-White Print" << endl;
  }
};

class ColorPrinter : public Printer {
public:
  // Override print() with specialized behavior
  void print() const {
    cout << "Vibrant High-Definition Color Print" << endl;
  }
};

int main() {
  Printer p;
  p.print();     // Standard Black-and-White Print

  ColorPrinter cp;
  cp.print();            // Vibrant Color Print
  cp.Printer::print();   // Explicit base version
  return 0;
}

Overriding Mechanics

A derived class defines a method with the exact same name and signature as the base to provide specialized behavior for that subtype.

Without virtual

Compile-time (static) resolution — pointer type decides

With virtual

Runtime (dynamic) resolution — actual object type decides

Part II · Inheritance

Type Conversion: Base & Derived

Upcasting · Base Pointers to Derived Objects · The Static Binding Problem

Upcasting: Base Pointers to Derived Objects

class Employee {
public:
  void introduce() const {
    cout << "I am a general employee." << endl;
  }
};

class Developer : public Employee {
public:
  void introduce() const {
    cout << "I am a C++ software architect." << endl;
  }
};

int main() {
  Developer dev;
  // UPCASTING: Always safe and implicit
  Employee* empPtr = &dev;

  // What does this print?
  empPtr->introduce();
  return 0;
}

The Static Binding Dilemma

Why Base Runs?

Without virtual, the compiler inspects the pointer type (Employee*) at compile time and binds Employee::introduce().

The Problem

We have a Developer object but the system behaves like a generic Employee. Upcasting alone is not enough for runtime dispatch.

The Solution

We need Virtual Functions & Polymorphism to inspect the actual object type at runtime — covered next.

Checkpoint: Inheritance Concepts & Mechanics

Question 1

If class Vehicle has protected int speed;, which functions can access speed?

  • A. Only member functions of Vehicle
  • B. Member functions of Vehicle and any derived classes
  • C. Any external function in main()
  • D. Only friend functions of Vehicle

Question 2

In what order do constructors execute when instantiating class Manager : public Employee?

  • A. Manager constructor runs first, then Employee
  • B. Employee constructor runs first, then Manager
  • C. Only Manager constructor runs
  • D. Both run concurrently in parallel threads

Question 3

What happens when a non-virtual overridden method is called via Base* ptr = new Derived();?

  • A. The derived class method is executed
  • B. The base class method is executed (static compile-time binding)
  • C. The program throws a runtime exception
  • D. The compiler refuses to build the code

Checkpoint: Inheritance Concepts & Mechanics — Answers

Q1: B — Vehicle and all derived classes ✓

protected makes the attribute private to external users while keeping it accessible to derived child classes. This is its core purpose — enabling inheritance without full exposure.

Q2: B — Employee first, then Manager ✓

Base classes are always constructed before derived classes. This guarantees that inherited members exist and are initialized before the child constructor's body begins to execute.

Q3: B — Base class method (static binding) ✓

Without virtual, C++ uses early (static) binding. The compiler binds the call based on the declared pointer type (Base*), ignoring the actual heap object type at runtime.

Part II · Inheritance

Multiple Inheritance

Multiple Base Classes · Ambiguity Issues · The Diamond Problem & Virtual Inheritance

Part II · Inheritance

Declaring Multiple Inheritance

Syntax · Combining Interfaces · Inheriting Multiple Base States

Multiple Inheritance: Syntax and Capabilities

class Printable {
public:
  void print() const {
    cout << "Rendering to paper..." << endl;
  }
};

class Serializable {
public:
  void serialize() const {
    cout << "Saving state to JSON..." << endl;
  }
};

// Invoice inherits from BOTH base classes
class Invoice : public Printable, public Serializable {
private:
  double amount;
public:
  Invoice(double a) : amount(a) {}
};

int main() {
  Invoice inv(500000);
  inv.print();     // from Printable
  inv.serialize(); // from Serializable
  return 0;
}

Design Insight

C++ allows a class to inherit capabilities from two or more base classes simultaneously using a comma-separated list in the class declaration.

Printable

Provides print() behavior

Serializable

Provides serialize() behavior

Invoice

Combines both capabilities + own data

Part II · Inheritance

The Diamond Problem

Ambiguity in Multi-Path Hierarchies · Resolving Duplicated Ancestors with Virtual Inheritance

The Diamond Problem and virtual Base Classes

// THE PROBLEM: Two copies of Device::id in SmartPhone!
class Device { public: int id; };
class Phone  : public Device {};
class Camera : public Device {};
class SmartPhone : public Phone, public Camera {};

int main() {
  SmartPhone sp;
  sp.id = 10; // COMPILE ERROR: Ambiguous!
}

// THE SOLUTION: Virtual Inheritance
class Device { public: int id; };
class Phone  : virtual public Device {};
class Camera : virtual public Device {};
class SmartPhone : public Phone, public Camera {};

int main() {
  SmartPhone sp;
  sp.id = 1001; // Exactly ONE shared Device!
  return 0;
}

Diamond Hierarchy

Part II · Inheritance

Abstract Base Classes

Pure Virtual Functions (= 0) · Incomplete Classes · Design Contracts

Part II · Inheritance

Declaring Abstract Base Classes

Pure Virtual Functions · Instantiation Prevention · Enforced Subtyping

Abstract Classes: Pure Contracts

class Shape {
public:
  // Pure virtual: defines WHAT, not HOW
  virtual double calcArea() const = 0;
  virtual double calcPerimeter() const = 0;
  virtual ~Shape() {} // Mandatory virtual destructor!
};

// Shape s; // COMPILE ERROR: Cannot instantiate!

class Circle : public Shape {
private:
  double radius;
public:
  Circle(double r) : radius(r) {}

  // Must implement all pure virtuals!
  double calcArea() const override {
    return 3.14159 * radius * radius;
  }
  double calcPerimeter() const override {
    return 2 * 3.14159 * radius;
  }
};

Abstract Class Rules

1

= 0 Syntax

A pure virtual function is declared with = 0 at the end. It defines the what, not the how.

2

Cannot Be Instantiated

Any class with at least one pure virtual function cannot be created directly with new or stack allocation.

3

Derived Must Implement

A derived class must override ALL pure virtual functions — otherwise it also becomes abstract.

4

Virtual Destructor

Always declare the base destructor as virtual when the class has virtual functions.

Part II · Inheritance

The C++ Interface Pattern

Pure Interfaces · Zero Data Members · Full Polymorphic Abstraction

Pure Interfaces: Plug-and-Play Architecture

// C++ Interface: all pure virtual, no data members
class IPayable {
public:
  virtual bool processPayment(double amount) = 0;
  virtual string getProviderName() const = 0;
  virtual ~IPayable() {}
};

class VNPTPayGateway : public IPayable {
public:
  bool processPayment(double amount) override {
    cout << "Processing " << amount
         << " VND via VNPT Pay..." << endl;
    return true;
  }
  string getProviderName() const override {
    return "VNPT Pay";
  }
};

class VietQRMethod : public IPayable {
public:
  bool processPayment(double amount) override {
    cout << "Generating VietQR for "
         << amount << " VND..." << endl;
    return true;
  }
  string getProviderName() const override {
    return "VietQR";
  }
};

Interface vs Abstract Class

Part II · Polymorphism

Polymorphism & Dynamic Binding

Static vs Dynamic Binding · The virtual Keyword · The vtable Mechanism

Part II · Polymorphism

Virtual Methods and Runtime Dispatch

The virtual Specifier · The override Specifier · Dynamic Binding at Runtime

Polymorphism: One Interface, Infinite Behaviors

class Animal {
public:
  virtual void speak() const {
    cout << "Generic creature sound" << endl;
  }
  virtual ~Animal() {}
};

class Cat : public Animal {
public:
  void speak() const override {
    cout << "Meow Meow!" << endl;
  }
};

class Dog : public Animal {
public:
  void speak() const override {
    cout << "Woof Woof!" << endl;
  }
};

void triggerSound(const Animal* a) {
  a->speak(); // Runtime dispatch!
}

int main() {
  Cat myCat;
  Dog myDog;
  triggerSound(&myCat); // Meow Meow!
  triggerSound(&myDog); // Woof Woof!
  return 0;
}

How virtual Changes Everything

virtual Keyword

Prefixing a method with virtual instructs the compiler to enable dynamic (late) binding for that function.

override Specifier

Use override in derived classes to tell the compiler to verify that you are actually overriding a virtual function — prevents silent name mismatches.

Runtime Dispatch

triggerSound receives an Animal* but calls the correct implementation based on the actual object type at runtime.

Part II · Polymorphism

Polymorphism in Practice

Heterogeneous Collections · Virtual Table (vtable) Machinery

Under the Hood: How the vtable Works

// Every class with virtual functions gets a hidden vptr
// Cat object in memory:
// ┌─────────────────────────┐
// │ vptr ──────────────────►│ vtable: Cat::speak() │
// ├─────────────────────────┤ └──────────────────────┘
// │ Cat-specific attributes │
// └─────────────────────────┘

// The 3-Step Runtime Dispatch:
// 1. Fetch object pointer: a
// 2. Follow a->vptr → Cat's vtable
// 3. Jump to Cat::speak()

// Virtual Destructor is MANDATORY:
class Base {
public:
  virtual ~Base() {} // Without this: derived destructor skipped!
};

vtable Mechanics

01

vptr Injection

The compiler injects a hidden pointer (vptr) into every object of a class that has virtual functions.

02

vtable Lookup

At runtime, vptr points to the class's virtual table — a lookup table of function addresses.

03

Function Jump

The correct function address is resolved from the vtable and called — dispatching to Cat::speak() even through an Animal*.

Chapter 3 Summary: The Complete OOP Architecture

Part I · Classes

Coursebook Chapter 5

Class & Object Concept

Blueprint vs Instance · Blueprint defines structure, instance holds state

Attributes & Methods

State (data members) + Behavior (member functions) — unified in one type

Encapsulation

private / protected / public modifiers — the golden rule of class design

Friend Functions & Classes

Selective privilege — unidirectional, non-inheritable exceptions

Constructors & Destructors

Initializer lists, overloading, RAII — deterministic lifecycle

Object Pointers & Arrays

new/delete, arrow operator (->), contiguous collections

Part II · Inheritance & Polymorphism

Coursebook Chapter 6

Inheritance Hierarchy

Single, multi-level, multiple — Is-A relationship and code reuse

Derivation Modes

public (99%), protected, private — controls access propagation

Constructor & Destructor Order

Base→Child construction, Child→Base destruction (LIFO)

Virtual Inheritance

Resolves the Diamond Problem — one shared ancestor instance

Abstract Classes & Interfaces

Pure virtual (= 0), C++ interface pattern — design contracts

Dynamic Polymorphism

virtual, override, vtable — runtime dispatch through base pointers

Workshop: MiniPOS V3.0 Architecture & Next Steps

MiniPOS V3.0 Implementation Blueprint

01

Base Class: Product

Encapsulated sku, price, stock with virtual display() and calcValue() — polymorphic foundation.

02

Derived Classes

FoodProduct (adds expiration date validation) and Electronics (adds warranty calculations) — specializations via override.

03

Interface IDiscountStrategy

Pure virtual calcDiscount(double subtotal) = 0 implemented by PercentDiscount and VipFixedDiscount.

04

Class Store

Manages a polymorphic collection Product* inventory[100] with automated dynamic dispatch at checkout.

Polymorphic Checkout Code

void Store::sell(int index, int qty,
                 IDiscountStrategy* promo) {
  double baseTotal =
    inventory[index]->getPrice() * qty;

  double finalTotal = baseTotal -
    (promo ? promo->calcDiscount(baseTotal) : 0);

  inventory[index]->deductStock(qty);

  cout << "Final Bill: "
       << finalTotal << " VND" << endl;
}