Banking System

Project Description
Design and implement a basic C++ banking application that models US-style accounts, transactions, and client relationships via Social Security Numbers (SSNs). The system must include:
1. Client Class
The Client class serves as the representation of an individual bank customer. Each client is uniquely identified by their personal information and may own multiple bank accounts. The class is designed to be shared among multiple accounts belonging to the same individual.
Attributes:
- clientName – Stores the full name of the client.
- SSN – A unique identifier (Social Security Number) used to associate clients with their bank accounts.
Purpose:
The Client object is referenced by all accounts belonging to the same person. This design allows centralized management of a client’s identity and easy retrieval of all related accounts.
2. BankAccount Class
The BankAccount class encapsulates the structure and functionality of an individual bank account. Each account is linked to a single client but a client may have multiple such accounts.
Attributes:
- accountNumber (std::string) – A unique identifier for each account.
- accountType – Describes the nature of the account, such as “Checking” or “Savings”.
- balance (double) – Represents the current available balance in the account.
- transactionHistory – A list of transaction records, each timestamped to ensure chronological tracking of activity.
- owner (std::shared_ptr) – A smart pointer referencing the client who owns the account.
Methods:
- deposit(amount) – Adds a specified amount to the current balance.
- withdraw(amount) – Deducts a specified amount from the account, ensuring overdrafts are handled appropriately.
- recordTransaction(details) – Logs each transaction with relevant information and a timestamp.
- display() – Outputs account details, including balance and recent transactions, in a user-readable format.
3. BankManager Class
The BankManager class acts as the central controller responsible for overseeing the bank’s operations. It maintains all accounts and provides administrative functions to manage them.
Responsibilities:
- accountExists(accountNumber) – Checks if an account with the given number exists in the system.
- addAccount(std::shared_ptr, accountNumber, type, initialDeposit) – Creates a new account associated with a client and initializes it with a starting balance.
- findAccount(accountNumber) – Retrieves a reference to the account object using its account number.
- removeAccount(accountNumber) – Deletes the specified account from the system.
- transfer(fromAccount, toAccount, amount) – Safely transfers funds between two accounts, ensuring both accounts are valid and the source has sufficient funds.
- getAccountsBySSN(ssn) – Returns a list of all accounts that belong to the client with the specified SSN.
- countAccountsBySSN(ssn) – Returns the total number of accounts linked to the provided SSN.
- displayAllAccounts() – Displays detailed information about every account managed by the bank.
Behavioral Requirements
1. Transaction Logging
Every financial operation, including account creation, deposits, withdrawals, and transfers, must be recorded in the account’s transaction history. Each entry should include a descriptive message and a timestamp to indicate when the operation occurred. This ensures transparency and accountability in financial activity.
2. Input Validation and Error Handling
The system must robustly handle invalid or potentially harmful operations. This includes:
- Preventing negative deposit or withdrawal amounts.
- Gracefully rejecting withdrawals that would result in an overdraft.
- Verifying that all account numbers referenced in operations exist.
- Ensuring funds are not transferred to or from non-existent accounts.
Solution :
Client Class :
// Include necessary headers
#include <iostream> // For input/output operations (e.g., std::cout)
#include <vector> // For using the std::vector container
#include <memory> // For using smart pointers like std::shared_ptr
#include <algorithm> // For algorithms like sort, find, etc. (not used yet but included)
#include <string> // For using std::string
#include <ctime> // For time-related functions (not used yet but included)
// Define a structure to represent a Client
struct Client {
// Shared pointers to strings representing the client's name and SSN (Social Security Number)
// std::shared_ptr allows multiple Clients to share ownership of the same string object.
std::shared_ptr<std::string> name;
std::shared_ptr<std::string> ssn;
// Constructor for Client
// Takes two shared_ptr<string> parameters by const reference to avoid copying
Client(const std::shared_ptr<std::string>& n,
const std::shared_ptr<std::string>& s)
: name(n), ssn(s) // Member initializer list initializes 'name' and 'ssn'
{}
};
BankAccount class :
// Define a class to represent a bank account
class BankAccount {
public:
// Shared pointers to owner's name and SSN
// Allows the same name/SSN to be shared across multiple accounts or linked clients
std::shared_ptr<std::string> ownerName;
std::shared_ptr<std::string> ownerSSN;
// Unique pointer to the account number
// Ensures exclusive ownership: each account has a unique number that cannot be shared
std::unique_ptr<std::string> accountNumber;
// Type of account (e.g., Checking, Savings)
std::string accountType;
// Current balance in the account
double balance;
// A history of transactions stored as strings
std::vector<std::string> transactionHistory;
// Constructor for BankAccount
BankAccount(const std::shared_ptr<std::string>& name,
const std::shared_ptr<std::string>& ssn,
const std::string& accNum,
const std::string& type = "Checking", // Default account type
double initialBalance = 0.0) // Default initial balance
: ownerName(name), ownerSSN(ssn),
accountNumber(std::make_unique<std::string>(accNum)), // Create a unique_ptr from accNum
accountType(type), balance(initialBalance)
{
// Record the creation of the account as the first transaction
recordTransaction("Account opened", initialBalance);
}
// Records a transaction with a timestamp, description, amount, and resulting balance
void recordTransaction(const std::string& description, double amount) {
time_t now = time(nullptr); // Get current time
char timestamp[64];
// Format the time into a human-readable string
strftime(timestamp, sizeof(timestamp), "%Y-%m-%d %H:%M:%S", localtime(&now));
// Append a detailed transaction string to the history
transactionHistory.push_back(
std::string(timestamp) + " | " + description +
" | Amount: $" + std::to_string(amount) +
" | Balance: $" + std::to_string(balance)
);
}
// Deposits money into the account
void deposit(double amount) {
// Validate deposit amount
if (amount <= 0) {
std::cerr << "Invalid deposit amount\n";
return;
}
// Update balance and record the transaction
balance += amount;
recordTransaction("Deposit", amount);
// Print confirmation
std::cout << "Deposited $" << amount << " | New balance: $" << balance << "\n";
}
// Withdraws money from the account
bool withdraw(double amount) {
// Validate withdrawal: must be positive and not exceed the balance
if (amount <= 0 || amount > balance) {
std::cerr << "Withdrawal failed\n";
return false;
}
// Deduct amount from balance and record the transaction
balance -= amount;
recordTransaction("Withdrawal", -amount);
// Print confirmation
std::cout << "Withdrew $" << amount << " | Remaining: $" << balance << "\n";
return true;
}
// Displays account information and last transaction
void display() const {
std::cout << "\nAccount #" << *accountNumber << " (" << accountType << ")\n"
<< "Client: " << *ownerName << "\n"
<< "SSN: " << *ownerSSN << "\n"
<< "Balance: $" << balance << "\n";
// Display last transaction if available
if (!transactionHistory.empty()) {
std::cout << "Last Transaction: " << transactionHistory.back() << "\n";
}
std::cout << "-------------------------\n";
}
};
BankManager class :
class BankManager {
// Container to store bank accounts using smart pointers
// std::unique_ptr ensures each BankAccount is exclusively owned and automatically cleaned up
std::vector<std::unique_ptr<BankAccount>> accounts;
public:
// Checks if an account with a given account number already exists
bool accountExists(const std::string& accNum) const {
// Uses std::any_of to scan through all accounts and compare account numbers
return std::any_of(accounts.begin(), accounts.end(),
[&accNum](const auto& acc) {
// Dereference accountNumber (unique_ptr<string>) to compare values
return *acc->accountNumber == accNum;
});
}
// Adds a new account for a given client if the account number is unique
void addAccount(const std::shared_ptr<Client>& client,
const std::string& accNum,
const std::string& type = "Checking", // Default account type
double initialDeposit = 0.0) // Default initial deposit
{
// Prevent account number duplication
if (accountExists(accNum)) {
std::cerr << "Error: Account number already exists\n";
return;
}
// Create and store a new BankAccount
accounts.push_back(std::make_unique<BankAccount>(
// Use client's name or fallback to empty string if null (safety check)
client->name ? client->name : std::make_shared<std::string>(""),
// Use client's SSN or fallback to empty string if null
client->ssn ? client->ssn : std::make_shared<std::string>(""),
accNum, type, initialDeposit
));
std::cout << "Account created successfully\n";
}
// Finds an account by account number and returns a raw pointer to the account
// Returns nullptr if not found
BankAccount* findAccount(const std::string& accNum) {
// Use std::find_if to locate the matching account
auto it = std::find_if(accounts.begin(), accounts.end(),
[&accNum](const auto& acc) {
return *acc->accountNumber == accNum;
});
// Return a raw pointer to the found account or nullptr if not found
return (it != accounts.end()) ? it->get() : nullptr;
}
// Removes an account with the specified account number
bool removeAccount(const std::string& accNum) {
// If account doesn't exist, return false immediately
if (!accountExists(accNum)) return false;
// Use std::remove_if to mark matching account for deletion,
// then erase it from the vector
accounts.erase(
std::remove_if(accounts.begin(), accounts.end(),
[&accNum](const auto& acc) {
return *acc->accountNumber == accNum;
}),
accounts.end()
);
return true; // Indicate successful deletion
}
// Transfers money from one account to another
bool transfer(const std::string& fromAcc,
const std::string& toAcc,
double amount)
{
// Locate source and destination accounts
BankAccount* source = findAccount(fromAcc);
BankAccount* dest = findAccount(toAcc);
// Both accounts must exist
if (!source || !dest) return false;
// Attempt withdrawal first; only deposit if withdrawal succeeds
if (source->withdraw(amount)) {
dest->deposit(amount);
return true;
}
return false; // Transfer failed
}
// Retrieves all accounts owned by a person with a specific SSN
std::vector<BankAccount*> getAccountsBySSN(const std::string& ssn) {
std::vector<BankAccount*> result;
// Use std::copy_if to collect pointers to matching accounts
std::copy_if(accounts.begin(), accounts.end(), std::back_inserter(result),
[&ssn](const auto& acc) {
return *acc->ownerSSN == ssn;
});
return result;
}
// Counts how many accounts belong to a given SSN
int countAccountsBySSN(const std::string& ssn) const {
// Uses std::count_if to count how many times the SSN appears
return std::count_if(accounts.begin(), accounts.end(),
[&ssn](const auto& acc) {
return *acc->ownerSSN == ssn;
});
}
// Displays information for all existing accounts
void displayAllAccounts() const {
std::cout << "\n===== ALL ACCOUNTS =====\n";
// Check if there are any accounts to display
if (accounts.empty()) {
std::cout << "No accounts available\n";
} else {
// Display each account using its own display method
std::for_each(accounts.begin(), accounts.end(),
[](const auto& acc) {
acc->display();
});
}
std::cout << "========================\n";
}
};
Explanation of the code :
Lambda Function
In C++, lambda functions are a concise way to define anonymous function objects directly in the scope where they are used. Introduced in C++11, a lambda function has the general syntax [capture](parameters) -> return_type { body }
, where the capture clause allows access to variables from the surrounding scope, parameters are the function arguments, and the body contains the logic to be executed. Lambda functions are especially useful for short operations like sorting, filtering, or callbacks, where defining a full function would be unnecessary. For example, std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });
uses a lambda to specify the comparison logic inline, making code more readable and localized. Lambdas can also be assigned to variables and invoked like regular functions, supporting both mutable state and generic types in modern C++ standards.
Used Predefined Functions :
Algorithm | Purpose | Example Capture |
---|---|---|
find_if | Locate the first element matching a condition | [&accNum] to match account number |
count_if | Count elements satisfying a predicate | [&ssn] to count accounts by SSN |
any_of | Check if any element meets a condition | [&accNum] to check for duplicates |
copy_if | Copy elements passing a filter into another container | [&ssn] to collect accounts by SSN |
for_each | Apply an operation to every element | [](auto &acc) to call display() |
transform | Map elements to new values (e.g., extract balances) | [&](auto &acc){ return acc->balance; } |
Internally, the system uses smart pointers and STL algorithms to keep ownership clear and operations safe. For example, each account’s data is managed by a std::unique_ptr<BankAccount>
, ensuring only the BankManager
ever deletes it, while client information is shared via std::shared_ptr<Client>
:
// Shared client pointer reused across accounts
auto clientPtr = std::make_shared<Client>("Alice", "111-22-3333");
bank.addAccount(clientPtr, "CHK1001", "Checking", 500);
bank.addAccount(clientPtr, "SAV2001", "Savings", 1000);
Looking up accounts returns a raw pointer, since ownership remains with the vector:
BankAccount* acc = bank.findAccount("CHK1001");
if (acc) {
// Directly modify balance
acc->deposit(150.0);
}
When transferring funds, the code chains methods while checking both pointers:
BankAccount* src = bank.findAccount("SAV2001");
BankAccount* dst = bank.findAccount("CHK1001");
if (src && dst && src->withdraw(300.0)) {
dst->deposit(300.0);
}
Finally, iterating over all accounts uses for_each
and lambdas to call display()
without manual indexing:
std::for_each(accounts.begin(), accounts.end(),
[](const auto& uptr) { uptr->display(); });
These patterns—smart pointers for memory, raw pointers for access, and STL algorithms for iteration—work together to keep the implementation concise, safe, and clear.
If you have any questions , feel free to contact us at [email protected]