C++ OOP and Templates

Build a BankAccount class

Description: Make a class BankAccount with a private balance. Add deposit and withdraw. Throw an exception for a negative deposit and for a withdraw bigger than the balance.

Hint:

  • Use std::invalid_argument and std::runtime_error.

Solution

#include <iostream>
#include <stdexcept>

class BankAccount {
public:
    void deposit(double amount) {
        if (amount <= 0) throw std::invalid_argument("Deposit must be positive");
        balance += amount;
    }
    void withdraw(double amount) {
        if (amount > balance) throw std::runtime_error("Not enough balance");
        balance -= amount;
    }
    double get_balance() const { return balance; }
private:
    double balance = 0;
};

int main() {
    BankAccount acc;
    acc.deposit(1000);
    acc.withdraw(250);
    std::cout << acc.get_balance() << '\n';
    try {
        acc.withdraw(5000);
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << '\n';
    }
    try {
        acc.deposit(-5);
    } catch (const std::exception& e) {
        std::cout << "Error: " << e.what() << '\n';
    }
}
Output

750
Error: Not enough balance
Error: Deposit must be positive

How it works

  • The balance is private, so only the two methods can change it, and both check the value.

Read Encapsulation.

Calculate areas with polymorphism

Description: Make an abstract class Shape with a pure virtual function area(). Write Circle (radius 2) and Rectangle (3 x 4). Put them in one container and print the total area.

Hint:

  • Use std::vector<std::unique_ptr<Shape>> and a virtual destructor.

Solution

#include <iomanip>
#include <iostream>
#include <memory>
#include <numbers>
#include <vector>

class Shape {
public:
    virtual ~Shape() = default;
    virtual double area() const = 0;
};

class Circle : public Shape {
public:
    explicit Circle(double r) : r(r) {}
    double area() const override { return std::numbers::pi * r * r; }
private:
    double r;
};

class Rectangle : public Shape {
public:
    Rectangle(double w, double h) : w(w), h(h) {}
    double area() const override { return w * h; }
private:
    double w, h;
};

int main() {
    std::vector<std::unique_ptr<Shape>> shapes;
    shapes.push_back(std::make_unique<Circle>(2));
    shapes.push_back(std::make_unique<Rectangle>(3, 4));

    double total = 0;
    for (const auto& s : shapes) total += s->area();
    std::cout << std::fixed << std::setprecision(2) << "Total area: " << total << '\n';
}
Output

Total area: 24.57

How it works

  • The loop uses the base type. C++ calls the area() of the real object (virtual function).
  • The virtual destructor makes sure that the right destructor runs when the smart pointers delete the objects.

Read Polymorphism and Abstract Classes.

Overload operators for a Money class

Description: Write a class Money which keeps rupees and paise. Overload +, == and <<. Add Rs. 10.75 and Rs. 5.50, and print Rs. 16.25.

Hint:

  • Keep the value in paise (a whole number) to avoid decimal errors.

Solution

#include <iostream>

class Money {
public:
    explicit Money(long long paise) : paise(paise) {}
    Money operator+(const Money& o) const { return Money(paise + o.paise); }
    bool operator==(const Money&) const = default;
    friend std::ostream& operator<<(std::ostream& os, const Money& m) {
        return os << "Rs. " << m.paise / 100 << '.' << (m.paise % 100 < 10 ? "0" : "") << m.paise % 100;
    }
private:
    long long paise;
};

int main() {
    Money a(1075), b(550);
    std::cout << a + b << '\n';
    std::cout << std::boolalpha << (a + b == Money(1625)) << '\n';
}
Output

Rs. 16.25
true

How it works

  • Money is kept as a whole number of paise, so there is no decimal rounding problem.
  • operator<< is a friend function, because the stream is on the left side.

Read Operator Overloading.

Write a generic maximum template

Description: Write a function template max_of which returns the bigger of two values. Use it with int, double and std::string. Then limit it to types which can be compared with a concept.

Hint:

  • std::totally_ordered is a ready concept for comparable types.

Solution

#include <concepts>
#include <iostream>
#include <string>

template <std::totally_ordered T>
T max_of(T a, T b) {
    return a > b ? a : b;
}

int main() {
    std::cout << max_of(3, 9) << '\n';
    std::cout << max_of(2.5, 1.5) << '\n';
    std::cout << max_of(std::string("pear"), std::string("apple")) << '\n';
}
Output

9
2.5
pear

How it works

  • The compiler makes one version of the function for each type you use.
  • The concept gives a clear error when the type can not be compared.

Read C++ Templates.