C++ Bank Management System Project
Multiple account types, deposits, transfers, and interest — a mini banking core.
What you will build
Grow the ATM idea into a small bank: many customers, savings and current accounts with different rules, transfers between accounts, and interest calculation. It's the classic OOP workout — inheritance and polymorphism doing real work, not decoration.
What you will learn
- Base classes and inheritance (Account → Savings / Current)
- Virtual functions for per-type rules
- Transfer logic that can't create or destroy money
- Persisting many objects to a file
Feature checklist
- Create savings and current accounts
- Deposit, withdraw, and check balance
- Transfer funds between accounts
- Savings earns interest; current allows a limited overdraft
- Transaction history per account
- All data saved to a file
Starter code
#include <iostream>
#include <string>
#include <memory>
#include <vector>
class Account {
protected:
std::string owner_; double balance_ = 0;
public:
Account(std::string o) : owner_(std::move(o)) {}
virtual ~Account() = default;
virtual bool withdraw(double amt) = 0; // each type: own rules
void deposit(double amt) { if (amt > 0) balance_ += amt; }
double balance() const { return balance_; }
const std::string& owner() const { return owner_; }
};
class Savings : public Account {
public:
using Account::Account;
bool withdraw(double amt) override {
if (amt <= 0 || amt > balance_) return false;
balance_ -= amt; return true;
}
void applyInterest() { balance_ *= 1.04; } // 4% yearly
};
class Current : public Account {
double overdraft_ = 200.0;
public:
using Account::Account;
bool withdraw(double amt) override {
if (amt <= 0 || amt > balance_ + overdraft_) return false;
balance_ -= amt; return true;
}
};
// Transfer: withdraw from one, deposit to the other;
// if the withdraw fails, nothing changes.Full walkthrough
Step 1: Design the Account base class
class Account holds what every account shares — number, holder name, balance — and the operations deposit() and withdraw(). The balance stays protected/private; subclasses and menus touch it only through methods, the ATM project's lesson scaled up.
Step 2: Derive Savings and Current with different rules
class Savings : public Account adds applyInterest() (say 5% a year, applied on demand). class Current : public Account overrides withdraw() to allow an overdraft down to -5000 and charges no interest. Same interface, different rules — that is polymorphism doing real work, not decoration.
class Savings : public Account {
public:
void applyInterest() { balance += balance * 0.05; }
};
class Current : public Account {
public:
bool withdraw(double amt) override; // allows overdraft to -5000
};Step 3: Store accounts polymorphically
Keep std::vector<std::unique_ptr<Account>> — unique_ptr from <memory> because a vector of Account VALUES would slice off the derived parts (a Savings stored by value becomes a plain Account and loses applyInterest). The base class needs a virtual destructor and virtual withdraw() for any of this to behave.
Step 4: Implement transfer as withdraw + deposit
A transfer finds both accounts by number, calls withdraw() on the source (its own rules apply — a Current account can transfer into its overdraft, a Savings cannot go negative), and only if that succeeds, deposit() into the destination. If the deposit cannot fail, the pair is effectively atomic; never deposit first, or a failed withdrawal creates money.
Step 5: Record a transaction history per account
Every successful deposit/withdraw/transfer appends a string (or small struct) to that account's history vector: 'DEPOSIT +10000 -> balance 60000'. Histories turn 'my money is wrong' from an argument into a lookup — print them from the account menu.
Step 6: Persist accounts with a type tag
Save one line per account starting with S or C so load() knows which class to construct — the factory decision is the interesting part: read the tag, make_unique<Savings> or make_unique<Current>, then fill the shared fields. Histories can be saved too, or rebuilt empty; decide and document it.
Step 7: Test the rules, not the happy path
Deposit and withdraw on both types; try to overdraw a Savings (refused) and a Current past -5000 (refused at the floor); apply interest to 100000 and expect exactly 105000; transfer between accounts and confirm the total money in the system is unchanged. Conservation of money is your test oracle.
Common mistakes
- Storing derived accounts by value in vector<Account>, slicing them into base accounts and losing their behaviour.
- Making withdraw() non-virtual, so a Current account quietly uses the base no-overdraft rule through a base pointer.
- Transfer implemented as deposit-then-withdraw, minting money whenever the withdrawal side refuses.
Compile and run
g++ -std=c++17 -o bank bank.cppSample run
$ ./bank
1) Open 2) Deposit 3) Withdraw 4) Transfer 5) Interest 6) History 0) Exit
> 1
Type (S)avings or (C)urrent:
> S
Holder name:
> Ada Lovelace
Account opened: #1001 (Savings)
1) Open 2) Deposit 3) Withdraw 4) Transfer 5) Interest 6) History 0) Exit
> 2
Account # and amount:
> 1001, 100000
Deposited. Balance: 100000.00
> 5
Interest on #1001 at 5%. New balance: 105000.00
> 3
Account # and amount:
> 1001, 200000
Refused: savings accounts cannot be overdrawn.
> 6
History #1001: DEPOSIT +100000.00 -> 100000.00 · INTEREST +5000.00 -> 105000.00
> 0Try it in your browser
No browser demo is included for this project. Build the C++ version below — that is the real program.