Return to Selected Works
systems/C++17 OOP

Vehicle Management System

“Building a fleet and rental system without hiding the complexity behind a framework or database.”

A pure C++17 console platform for fleet operations, polymorphic rental contracts, post-return inspection auditing, and flat-file persistence.

Role
Lead Systems Architect
Context
4 Weeks (Semester 2 OOP Lab)
Team
4 Members (UET Taxila)
Core Stack
C++17, Inheritance, Polymorphism, Flat-File I/O
Vehicle Management System

Fig 1.0 — Architecture execution snapshot (Vehicle Management System)

The Friction

Why build a vehicle management system from scratch in console C++?

Introductory computer science coursework frequently relies on sterile, frictionless object-oriented examples: shapes calculating their own areas, or animal hierarchies with barking dogs. These examples teach class syntax while concealing the real operational friction of software: state transitions, file serialization, memory leaks, and input stream failures.

I wanted to build a system where architectural decisions had real consequences. In a fleet management platform, vehicles change state across rentals, inspections, and sales. Calculations must dynamically discount long rentals, penalize structural damage, and serialize state cleanly to disk without an ORM or a database engine to rescue the design.

Deliberate Constraints

The system architecture was not chosen in an unconstrained vacuum. Each structural decision emerged directly from four non-negotiable technical boundaries.

[NO DATABASE ENGINE]

Zero SQLite, Postgres, or embedded database drivers. All persistence relies strictly on local file streams.

Architectural Outcome

Required designing a custom pipe-delimited (|) flat-file format with atomic load and serialize routines executed on application startup and teardown.

[NO GUI FRAMEWORK]

Strict console and terminal execution across both Windows (MSVC) and Linux (GCC).

Architectural Outcome

Mandated ANSI color codes, columnar ASCII formatting tables, and strict cin stream failbit recovery to handle invalid keystrokes without crashes.

[NO EXTERNAL DEPENDENCIES]

Restricted exclusively to standard ISO C++17 library headers (<iostream>, <vector>, <fstream>, <memory>, <algorithm>).

Architectural Outcome

Every validation routine, string splitting parser, search algorithm, and transaction logger had to be built and maintained from scratch.

[POLYMORPHIC OWNERSHIP]

Heterogeneous fleet held as raw base pointers in std::vector<Vehicle*>.

Architectural Outcome

Enforced strict virtual destructor contracts (virtual ~Vehicle()) and explicit manual heap management during shutdown to eliminate undefined behavior and memory leaks.

System Architecture & Data Pipeline

The system is structured around three orthogonal concerns: domain entities (Vehicle, User, Transaction hierarchies), storage serialization (FileHandler), and interactive flow orchestration (MenuHandler). Pure virtual interfaces guarantee dynamic dispatch at runtime.

Runtime Dispatch via Virtual Method Table (vtable)
<<Abstract Base>> VehicleInclude/Vehicle.h
- vehicleID: string | model: string | rentalRate: float
- status: VehicleStatus (Available | Rented | Sold)
+ virtual ~Vehicle(); // Mandatory for polymorphic delete
+ virtual calculateCost(int days) = 0;
+ virtual getCategory() const = 0;
EconomyIDs 3000s

Alto, Cultus, Corolla. Standard tiered rental base.

calcCost: days * baseRate
LuxuryIDs 4000s

Audi A6, BMW 7, Land Cruiser. Chauffeur insurance rate.

calcCost: days * baseRate * 1.25
SUVIDs 5000s

Sportage, Tucson, Fortuner. All-terrain security deposit.

calcCost: days * baseRate + terrainFee
VanIDs 6000s

Bolan, Hiace, Coaster. High-capacity commercial rate.

calcCost: days * baseRate (cap > 15)

Dynamic Polymorphism at Runtime: The orchestrator holds a single container std::vector<Vehicle*> fleet. When executing reservations or computing quotes, method calls to v->calculateCost(days) dynamically dispatch to the concrete subclass implementation through each instance's vtable pointer.

Subsystem Decomposition

Polymorphic Fleet Subsystem

Vehicle Base & 4 Derived Categories

Encapsulates core vehicle attributes (ID, model, seating capacity, base rate, status enum) and exposes pure virtual methods for dynamic cost calculations.

Impl: Derived classes (Economy, Luxury, SUV, Van) implement virtual calculateCost(days) with category-specific logic and provide specialized hooks for UI template method rendering.

Flat-File Storage Engine

FileHandler & Delimited Streams

Cold-start ingestion and clean shutdown persistence across four system text files (Vehicle.txt, Users.txt, Transactions.txt, Inspections.txt).

Impl: Reads pipe-delimited records token by token, inspects category codes to dynamically instantiate the correct C++ derived subclass on the heap, and commits atomic state back to disk on exit.

Dual-Role Session Controller

Admin (1000+) vs Customer (2000+)

Enforces privilege boundaries across two user classes. Admins access fleet management, sale transactions, and audit logs. Customers access the trip planner, active booking, and personal history.

Impl: An abstract User base class with pure virtual showMenu() and getRole() dispatches into specialized session loops for Admin and Customer instances.

Trip Planner & Pricing Engine

SearchEngine & Financial Logic

Evaluates dynamic fleet arrays against client budget caps, target passenger counts, and travel distances; applies tiered long-term discounts and post-return damage multipliers.

Impl: Tiered discounts automatically deduct 10% for 4–7 rental days and 20% for 8+ days. Post-rental inspections levy 50% or 200% surcharges on structural damage.

The Hard Part: Polymorphic Destruction & Container Lifecycle

How an absent virtual destructor quietly breaks memory ownership in heterogeneous collections.

In main.cpp, the entire fleet is loaded into a single heterogeneous container: std::vector<Vehicle*> fleet. When the program exits, the shutdown routine iterates through every pointer to free allocated heap memory with delete v.

In C++, destructors are not virtual by default. If a base class destructor is non-virtual, calling delete on a base pointer (Vehicle*) that points to a derived instance (such as Luxury or SUV) triggers undefined behavior under ISO C++ §5.3.5/3. The compiler dispatches only ~Vehicle(); the derived class destructor never runs. Any memory or resources held by the derived subclass are leaked, and the heap memory tracking structures can be corrupted.

Include/Vehicle.h & Source/main.cpp
cpp
// Include/Vehicle.h - Base Definition
class Vehicle {
protected:
    string vehicleID;
    string model;
    int capacity;
    float rentalRate;
    VehicleStatus status;

public:
    Vehicle(string id, string model, int capacity, float rate);

    // CRITICAL: Virtual destructor ensures dynamic dispatch down
    // to ~Economy(), ~Luxury(), etc. through the virtual method table (vtable).
    virtual ~Vehicle();

    // Pure virtual interfaces
    virtual float calculateCost(int days) = 0;
    virtual string getCategory() const = 0;
};

// Source/main.cpp - Polymorphic Teardown
for (Vehicle* v : fleet) {
    delete v; // Dispatches safely to the correct derived destructor
}
fleet.clear();
Explicitly declaring virtual ~Vehicle() places the destructor address in the vtable, guaranteeing safe polymorphic deallocation.
The Technical Resolution

By declaring virtual ~Vehicle() in the base class header, C++ ensures that the vtable lookup resolves to the derived destructor (~Economy(), ~Luxury(), etc.) before the base destructor chain unwinds. In addition, we addressed cross-platform text parsing: carriage returns (\r\n on Windows vs \n on Linux) were corrupting tokenized string IDs until a universal trim utility was introduced in FileHandler::split().

What the System Taught Me

C++ does not provide training wheels. Inheritance is not merely a syntactic convenience for code reuse; it is a direct contract on memory layouts, vtable pointer offsets, and object lifecycles.

Terminal Execution & Tiered Pricing Verification

Recorded console execution showing clean fleet ingestion from Vehicle.txt and real-time calculation of tiered rental discounts.

Launch Interactive Shell
hmsaeed@taxila: ~/projects/vms (x86_64-gcc)
C++17
$./build/VehicleManagementSystem
[SYSTEM] Loading Vehicle.txt records... 16 vehicles verified.
[SYSTEM] Loading Users.txt database... 6 accounts active.
===========================================================
VEHICLE MANAGEMENT & RENTAL ORCHESTRATION
C++17 CONSOLE
===========================================================
Authenticated: Customer #2001 (Saeed)
[TRIP PLANNER] Filter parameters: Budget Rs. 40,000 | Passengers >= 4
Matches found: 5 available vehicles
Selected: [ID: 3004] Honda City Aspire (Economy)
Base Daily Rate: Rs. 6,500/day | Rental Duration: 5 Days
Gross Calculation: 5 x 6,500 = Rs. 32,500
[TIER DISCOUNT] Duration in 4-7 day bracket: 10% OFF applied (-Rs. 3,250)
Net Payable Balance: Rs. 29,250
[TRANSACTION LOGGED] Tx #9042 committed to Transactions.txt
[SHUTDOWN] Saving state to disk and releasing 16 heap allocations... DONE.
$

Engineering Reflection

“Writing raw C++ without a framework forces you to stop thinking about features and start thinking about memory boundaries, object ownership, and failure modes.”

When working with modern web stacks, hundreds of abstraction layers shield you from physical machine realities. An ORM prepares your SQL queries, a garbage collector quietly recovers neglected memory allocations, and a web framework swallows malformed inputs before they reach application code.

In a zero-dependency C++ console program, none of those safety nets exist. If an end user inputs a character string when the console prompt expects an integer ID, std::cin trips its failbit and locks the input stream into a perpetual failure loop until you manually call cin.clear() and cin.ignore(). If you omit a virtual destructor on your abstract base, your program exits with silent memory corruption.

Building this platform taught me that real engineering is not measured by how many external packages you can knit together, but by whether you understand how your software behaves when inputs fail, memory must be reclaimed, and the system is stripped of all convenience.

Interested in discussing this architecture?

I'm always open to technical dialogue, code reviews, and exploring system constraints.

Start a Technical Conversation→