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

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.
Zero SQLite, Postgres, or embedded database drivers. All persistence relies strictly on local file streams.
Required designing a custom pipe-delimited (|) flat-file format with atomic load and serialize routines executed on application startup and teardown.
Strict console and terminal execution across both Windows (MSVC) and Linux (GCC).
Mandated ANSI color codes, columnar ASCII formatting tables, and strict cin stream failbit recovery to handle invalid keystrokes without crashes.
Restricted exclusively to standard ISO C++17 library headers (<iostream>, <vector>, <fstream>, <memory>, <algorithm>).
Every validation routine, string splitting parser, search algorithm, and transaction logger had to be built and maintained from scratch.
Heterogeneous fleet held as raw base pointers in std::vector<Vehicle*>.
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.
Alto, Cultus, Corolla. Standard tiered rental base.
Audi A6, BMW 7, Land Cruiser. Chauffeur insurance rate.
Sportage, Tucson, Fortuner. All-terrain security deposit.
Bolan, Hiace, Coaster. High-capacity commercial rate.
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 CategoriesEncapsulates core vehicle attributes (ID, model, seating capacity, base rate, status enum) and exposes pure virtual methods for dynamic cost calculations.
Flat-File Storage Engine
FileHandler & Delimited StreamsCold-start ingestion and clean shutdown persistence across four system text files (Vehicle.txt, Users.txt, Transactions.txt, Inspections.txt).
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.
Trip Planner & Pricing Engine
SearchEngine & Financial LogicEvaluates dynamic fleet arrays against client budget caps, target passenger counts, and travel distances; applies tiered long-term discounts and post-return damage multipliers.
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 - 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();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().
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.
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.