Bank 2 — Classes and encapsulation¶
Twelve exercises on bundling data with behaviour, and on making an invariant impossible to break from outside. Modules 2 and 4. Levels L1–L6.
Solutions are in Solutions and answers; nudges are in Hints. Attempt first.
Exercises¶
E12 — Say what an invariant is L1 concept¶
A BankAccount promises that balance is never negative.
Task. (a) Define invariant in one sentence. (b) Give two more invariants a real bank account has that the first sentence does not mention. (c) For each, name the single operation that must enforce it.
E13 — Finish the class L2 complete¶
#include <iostream>
#include <string>
class Product {
public:
Product(std::string name, double price) : name_(name), price_(price) {}
std::string name() const { return name_; }
// TODO: a const member function price() returning price_
// TODO: a member function apply_discount(double factor) that multiplies
// price_ by factor and does nothing if factor is not in (0, 1]
private:
std::string name_;
double price_;
};
int main() {
Product p{ "Notebook", 4.00 };
p.apply_discount(0.75);
std::cout << p.name() << ": " << p.price() << '\n';
}
Task. Supply the missing members so the program prints Notebook: 3.
E14 — Initialisation order L2 predict¶
#include <iostream>
class Box {
public:
Box() : b_(a_ + 1), a_(5) { std::cout << a_ << ' ' << b_ << '\n'; }
private:
int a_;
int b_;
};
int main() {
Box box;
}
Task. This program triggers GCC's -Wreorder warning, because the initialiser list
names b_ before a_ while the members are declared the other way round. Compile it
with -Wall (not the course's -Werror) to see the warning. Then write the output
anyway, state the rule that decides it, and say in one sentence what the warning is
telling the author to do.
E15 — The getter that cannot be called L3 debug scenario¶
A reporting tool reads a temperature sensor and prints its value. It will not build.
#include <iostream>
class Sensor {
public:
Sensor(double celsius) : celsius_(celsius) {}
double celsius() { return celsius_; }
private:
double celsius_;
};
void report(const Sensor& s) {
std::cout << "temperature: " << s.celsius() << '\n';
}
int main() {
Sensor s{21.5};
report(s);
}
Task. Explain why report cannot call celsius() on its const reference, then
fix it. Which single keyword was missing, and why is adding it always correct for
an accessor?
E16 — Enforce the rule, not the caller L3 modify scenario¶
A booking desk holds appointment slots. A slot has a start hour (0–23) and a duration in minutes (15–120). The current class lets any value through.
#include <iostream>
class Slot {
public:
Slot(int hour, int minutes) : hour_(hour), minutes_(minutes) {}
int hour() const { return hour_; }
int minutes() const { return minutes_; }
private:
int hour_;
int minutes_;
};
int main() {
Slot good{9, 30};
Slot bad{99, 5}; // nonsense, but accepted
std::cout << good.hour() << ' ' << bad.hour() << '\n';
}
Task. Make the constructor reject out-of-range values. Decide where the check belongs and what should happen when it fails; justify your choice of failure mechanism (a flag, an error code, or an exception) in one sentence.
E17 — Close the door L3 modify scenario¶
A member record exposes its fields directly:
#include <iostream>
#include <string>
class Member {
public:
std::string id;
std::string name;
int loansOut;
};
int main() {
Member m{ "M-100", "Bilal", 2 };
m.loansOut = -5; // impossible, but compiles
std::cout << m.name << " " << m.loansOut << '\n';
}
Task. Rewrite Member so that no outside code can put it into an impossible
state, while still allowing callers to (a) read the id and name, and (b) record a
loan being returned. Say which operations you kept public and why.
E18 — Deposits and withdrawals L2 trace scenario¶
#include <iostream>
#include <string>
class Account {
public:
Account(std::string owner, double balance)
: owner_(owner), balance_(balance) {}
bool withdraw(double amount) {
if (amount <= 0.0 || amount > balance_) return false;
balance_ -= amount;
return true;
}
void deposit(double amount) {
if (amount > 0.0) balance_ += amount;
}
double balance() const { return balance_; }
private:
std::string owner_;
double balance_;
};
int main() {
Account a{ "Hina", 100.0 };
std::cout << a.withdraw(30.0) << '\n';
std::cout << a.withdraw(500.0) << '\n';
a.deposit(-10.0);
std::cout << a.balance() << '\n';
}
Task. Three lines of output, exactly. Then: which single line of the class is the one that keeps the no-negative-balance invariant true?
E19 — Find the leaks L4 analyze¶
#include <string>
class Session {
public:
Session(std::string user, int minutes) : user_(user), minutes_(minutes) {}
std::string& user() { return user_; } // (1)
int& minutes() { return minutes_; } // (2)
std::string user() const { return user_; } // (3)
int minutes() const { return minutes_; } // (4)
void extend(int by) { minutes_ += by; } // (5)
private:
std::string user_;
int minutes_;
};
Task. Identify every line that lets an outside caller break the class's intent, and say what a caller could do with each that the author did not intend. Which of these are compile-time leaks and which are merely poor taste?
E20 — The invariant that isn't L4 debug¶
#include <iostream>
#include <vector>
class Team {
public:
void add(int strength) {
if (strength > 0) members_.push_back(strength);
}
std::vector<int>& members() { return members_; } // the door
int total() const {
int sum = 0;
for (int s : members_) sum += s;
return sum;
}
private:
std::vector<int> members_;
};
int main() {
Team t;
t.add(5);
t.add(8);
t.members().push_back(-100); // oops
std::cout << t.total() << '\n';
}
Task. What does this print, and what invariant was the class trying to protect?
Repair Team so the invariant holds even against a hostile caller, without removing
the ability to inspect the members.
E21 — Const all the way down L4 predict¶
#include <iostream>
class Counter {
public:
Counter() : count_(0) {}
int count() const { return count_; }
void increment() { ++count_; }
private:
int count_;
};
void show(const Counter& c) {
std::cout << c.count() << '\n';
}
int main() {
Counter c;
c.increment();
c.increment();
show(c);
Counter& r = c;
r.increment();
show(r);
}
Task. Write the two printed lines. Then explain why show takes const Counter&
and what would happen if it took Counter&.
E22 — Design a lending desk L5 design class scenario¶
A small library desk hands out one item per member at a time. An item has a catalogue number and a title. A member has a name and may hold at most one item. When an item is lent, the desk records which member holds it; when it is returned, the desk forgets the holder.
Task. Design the class or classes for this. Give the exact class names, their public operations (signatures ok, bodies not needed) and one sentence per class stating its responsibility. State the invariant(s) the design protects and the one operation that enforces each.
How you will be judged. On whether your design makes the "at most one item" rule impossible to break from outside, and on whether you made a decision rather than hedging with two ways to do everything.
E23 — Split the god object L6 refactor scenario¶
A single Library class has grown to hold items, members, loans, opening hours,
late fees and the printed menu. Every new feature edits this one class.
Task. Write a short design note that:
- names each distinct responsibility currently tangled inside
Library, one line each; - proposes the classes you would split it into, and which class depends on which;
- names one thing that must stay in
Librarybecause it genuinely coordinates; - states which single change would be hardest and why you would do it last.
Success. The note is a plan, not a wish-list: a reader could execute it without guessing your intent.
Previous topic: Mindset and compilation · Next topic: Constructors and lifecycle · Hints: hints.md · Solutions: solutions.md