Blueprint · Attributes · Methods
Private · Protected · Public
Base · Derived · Hierarchy
Virtual · Override · vtable
Pure Virtual · Interfaces
Definition, instantiation, and object usage
Attributes (data members) and methods (member functions)
Access modifiers, friend functions & friend classes
Initialization, overloading, and object lifecycle
Dynamic allocation, arrow operator, collections
Base & derived classes, derivation modes
Execution order and initialization lists
Protected access, overriding, upcasting
Multiple bases, ambiguity, Diamond Problem
Pure virtual functions and C++ interface pattern
Virtual functions, override, and vtable mechanism
Define classes with encapsulated data (private), controlled interfaces (public), and constructor initializer lists.
Correctly apply access modifiers and declare friend functions/classes when justified.
Allocate and deallocate objects and arrays dynamically using new, delete, and the arrow operator.
Implement single, multi-level, and multiple inheritance while preventing the Diamond Problem.
Use virtual functions and base class pointers for runtime dynamic binding with vtable dispatch.
Formulate pure virtual contracts (= 0) and build modular systems like MiniPOS V3.0.
struct BankAccount {
string owner;
double balance;
};
BankAccount acc = {"Alice", 1000000};
acc.balance = -999999; // Anyone can corrupt!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.
Private fields
Public methods
Business logic
Definition of Classes · Instantiation of Objects · Blueprint vs Instance
Class Syntax · Member Declarations · Semicolon Rule
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.
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 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!Use the class keyword followed by the class name in PascalCase.
Enclose all member declarations in { ... }.
The closing brace must end with ;. Omitting it causes compiler errors.
Unlike struct (defaults to public), a class defaults all members to private.
Instantiating Objects · The Dot Operator · Stack vs Heap Instantiation
#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;
}Accesses public methods and variables on an object instance: item1.display()
The compiler blocks any external access to private members. Invalid values cannot be forced into price.
Data Representation (State) · Functional Operations (Behavior)
Storing State · Types of Attributes · Memory Footprint per Instance
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.
studentId: "B23DCCN001"
fullName: "Nguyen Van An"
midtermScore: 8.5
finalScore: 7.5
studentId: "B23DCCN002"
fullName: "Le Thi Binh"
midtermScore: 9.0
finalScore: 9.5
In-Class Definitions · Scope Resolution (::) · The const Method Qualifier
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;
}Place clean declarations inside the class (or .h header file) for a compact interface.
Implement methods using ClassName::MethodName in a .cpp source file.
Mark methods that only read data with const to guarantee const-correctness and allow calls on const objects.
What happens if you define a class in C++ without any access modifier specified for its members?
Why should read-only member functions (like calcArea()) be qualified with const?
What is the operator used to define a class method outside of the class body?
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++.
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.
The syntax ReturnType ClassName::MethodName(params) tells the compiler that MethodName belongs to the scope of ClassName. The :: operator resolves namespace membership.
Access Modifiers · Encapsulation Safeguards · Friend Functions & Classes
Private · Protected · Public — Controlling Data Visibility · Defense-in-Depth for Object State
class VaultAccount {
private:
double secretBalance; // ONLY within VaultAccount
protected:
string accountTier; // VaultAccount + child classes
public:
string ownerName; // Accessible by any code
};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;
}
};acc.balance = -1000 is impossible — field is private.
Withdrawals cannot exceed the existing balance. The class owns its rules.
All mutations pass through validated setters, making bugs easy to trace.
Controlled Encapsulation Exceptions · The friend Keyword
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;
}
};Account cannot access Transaction's private data just because Transaction is Account's friend.
Child classes of Transaction do NOT inherit the friendship privilege.
Friendship is a deliberate coupling. Over-use defeats encapsulation and makes code harder to maintain.
Object Initialization · Member Initializer Lists · Destructor Cleanups
Automatic Lifecycle Initialization · Default & Parameterized Constructors
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
}
};The constructor identifier must match the class name exactly.
Not even void — the compiler handles this automatically.
Initializes members directly during memory allocation — avoids redundant default-construction + re-assignment. Required for const and reference members.
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;
}No arguments — default Guest order
ID + name — open order, no total yet
ID + name + total — complete record
Object Deallocation · Resource Release · Automatic Cleanup Order
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;
}
};Named ~ClassName() — automatically recognized by the compiler.
Takes no arguments, has no return type. A class can have only ONE destructor.
C++ guarantees destructors execute even when functions exit early or throw exceptions — preventing memory and file handle leaks.
If class Beta is declared as a friend of class Alpha, which is true?
When stack objects go out of scope, in what order do their destructors run?
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.
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.
Heap Allocation (new/delete) · Pointer Access (->) · Array of Instances
Pointers to Classes · Heap Instantiation · Arrow Operator (->)
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;
}The arrow operator is syntactic sugar for dereferencing followed by member access:
Points to stack memory. Memory freed automatically when scope ends.
Points to heap memory. Must call delete explicitly to avoid memory leaks.
Fixed Arrays of Instances · Initialization · Iterating Collections
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;
}ID: 101, Name: "Nguyen Van A"
ID: 102, Name: "Tran Thi B"
ID: 103, Name: "Le Van C"
The "Is-A" Relationship · Base & Derived Classes · Code Reuse Without Duplication
Derivation Syntax · Establishing Parent-Child Hierarchies
// 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;
}
};Developer automatically has name and baseSalary — no duplication needed.
A Developer IS AN Employee. Anywhere an Employee is expected, a Developer can be used.
Developer adds techStack on top of the base — specialization without modification.
Public, Protected, and Private Inheritance Modes
Represents true subtyping ("Is-A"). An instance of Developer can substitute for an Employee wherever needed.
Private members of the base class are never directly accessible in the child, regardless of which derivation mode is chosen.
Constructor Chaining · Base Initialization Order · Reverse Destruction Order
Explicit Base Construction · Initialization Sequencing
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 class constructor runs first to establish the foundation — initializing inherited members.
Once the parent foundation is ready, the derived constructor executes its body to add specialized behavior.
LIFO Destruction · Cleaning Child Resources First
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;
}Foundation is built before specialized features.
Child adds features on top of the ready base.
Child may depend on base resources while cleaning up — must release its own first.
Foundation is dismantled only after all dependents are done.
Protected Members · Method Overriding · Upcasting Type Conversions
The protected Specifier · Resolving Hidden Scope with Base::
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;
}
};Bridges complete encapsulation (private) and full exposure (public). Accessible to the class and its children — not to outsiders.
Use Account::printStatement() to explicitly call the base version of an overridden method from within the child.
Redefining Behaviors · Static Name Hiding
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;
}A derived class defines a method with the exact same name and signature as the base to provide specialized behavior for that subtype.
Compile-time (static) resolution — pointer type decides
Runtime (dynamic) resolution — actual object type decides
Upcasting · Base Pointers to Derived Objects · The Static Binding Problem
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;
}Without virtual, the compiler inspects the pointer type (Employee*) at compile time and binds Employee::introduce().
We have a Developer object but the system behaves like a generic Employee. Upcasting alone is not enough for runtime dispatch.
We need Virtual Functions & Polymorphism to inspect the actual object type at runtime — covered next.
If class Vehicle has protected int speed;, which functions can access speed?
In what order do constructors execute when instantiating class Manager : public Employee?
What happens when a non-virtual overridden method is called via Base* ptr = new Derived();?
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.
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.
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.
Multiple Base Classes · Ambiguity Issues · The Diamond Problem & Virtual Inheritance
Syntax · Combining Interfaces · Inheriting Multiple Base States
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;
}C++ allows a class to inherit capabilities from two or more base classes simultaneously using a comma-separated list in the class declaration.
Provides print() behavior
Provides serialize() behavior
Combines both capabilities + own data
Ambiguity in Multi-Path Hierarchies · Resolving Duplicated Ancestors with Virtual Inheritance
// 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;
}
Pure Virtual Functions (= 0) · Incomplete Classes · Design Contracts
Pure Virtual Functions · Instantiation Prevention · Enforced Subtyping
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;
}
};A pure virtual function is declared with = 0 at the end. It defines the what, not the how.
Any class with at least one pure virtual function cannot be created directly with new or stack allocation.
A derived class must override ALL pure virtual functions — otherwise it also becomes abstract.
Always declare the base destructor as virtual when the class has virtual functions.
Pure Interfaces · Zero Data Members · Full Polymorphic Abstraction
// 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";
}
};Static vs Dynamic Binding · The virtual Keyword · The vtable Mechanism
The virtual Specifier · The override Specifier · Dynamic Binding at Runtime
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;
}Prefixing a method with virtual instructs the compiler to enable dynamic (late) binding for that function.
Use override in derived classes to tell the compiler to verify that you are actually overriding a virtual function — prevents silent name mismatches.
triggerSound receives an Animal* but calls the correct implementation based on the actual object type at runtime.
Heterogeneous Collections · Virtual Table (vtable) Machinery
// 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!
};The compiler injects a hidden pointer (vptr) into every object of a class that has virtual functions.
At runtime, vptr points to the class's virtual table — a lookup table of function addresses.
The correct function address is resolved from the vtable and called — dispatching to Cat::speak() even through an Animal*.
Blueprint vs Instance · Blueprint defines structure, instance holds state
State (data members) + Behavior (member functions) — unified in one type
private / protected / public modifiers — the golden rule of class design
Selective privilege — unidirectional, non-inheritable exceptions
Initializer lists, overloading, RAII — deterministic lifecycle
new/delete, arrow operator (->), contiguous collections
Single, multi-level, multiple — Is-A relationship and code reuse
public (99%), protected, private — controls access propagation
Base→Child construction, Child→Base destruction (LIFO)
Resolves the Diamond Problem — one shared ancestor instance
Pure virtual (= 0), C++ interface pattern — design contracts
virtual, override, vtable — runtime dispatch through base pointers
Encapsulated sku, price, stock with virtual display() and calcValue() — polymorphic foundation.
FoodProduct (adds expiration date validation) and Electronics (adds warranty calculations) — specializations via override.
Pure virtual calcDiscount(double subtotal) = 0 implemented by PercentDiscount and VipFixedDiscount.
Manages a polymorphic collection Product* inventory[100] with automated dynamic dispatch at checkout.
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;
}
Chapter 3 — Object-Oriented Programming with C++