5.2.5 · D4C++ Programming

Exercises — Classes — member functions, access specifiers (public, private, protected)

1,913 words9 min readBack to topic

Every solution assumes the compiler is your bouncer: if a line breaks an access wall, it is rejected at compile time — it never runs. Look at the wall diagram below whenever you need to remember who may touch what.

Figure — Classes — member functions, access specifiers (public, private, protected)

Level 1 — Recognition

(Can you read the rules straight off the page?)

L1.1

State the access level (public / private / protected) of each member below, given no keyword is written before it.

struct Point { double x; void move(); };
class Box   { double w; void grow(); };
Recall Solution

The rule: struct defaults to public, class defaults to private. Nothing here changes that default, so:

  • Point::xpublic · Point::movepublic
  • Box::wprivate · Box::growprivate

What it looks like: on the wall diagram, Point's members sit in the open blue zone; Box's members sit behind the red private wall.

L1.2

For each access specifier, tick which of {same class, derived class, outside main} can reach it.

Recall Solution

Straight from the table in the parent note:

Specifier Same class Derived Outside
public
protected
private

Mnemonic: Public Pals, Protected Progeny, Private Personal.


Level 2 — Application

(Apply the rules to concrete code.)

L2.1

Which lines compile, which fail, and why?

class Account {
private:
    double balance = 0;
public:
    void deposit(double a) { balance += a; }
    double get() const { return balance; }
};
 
int main() {
    Account a;
    a.deposit(50);        // (1)
    a.balance += 10;      // (2)
    double b = a.get();   // (3)
    a.balance = a.get();  // (4)
}
Recall Solution
  • (1) compilesdeposit is public, called from outside. ✅
  • (2) FAILSbalance is private; main is outside the class. ❌
  • (3) compilesget is public. ✅
  • (4) FAILS — the left side a.balance touches a private member from outside. ❌ (The right side a.get() is fine, but ONE illegal token kills the line.)

After the two legal lines run, the balance would logically be . There is no runtime output here — the point is which lines the bouncer lets past the door.

L2.2

Complete withdraw so it never lets the balance go negative. Then compute the balance after this sequence: deposit(100); withdraw(30); withdraw(200); deposit(5);.

void withdraw(double a) {
    // fill in: reject if it would overdraw
}
Recall Solution

The only door into the data must also be the guard:

void withdraw(double a) {
    if (a > balance) return;   // reject overdraft, do nothing
    balance -= a;
}

Trace it starting from :

  • deposit(100) → balance
  • withdraw(30), allowed → balance
  • withdraw(200), rejected → balance stays
  • deposit(5) → balance

Final balance . The rejected withdrawal proves the guard works: an invalid state (negative balance) is made unrepresentable.


Level 3 — Analysis

(Reason about corner cases and mechanisms.)

L3.1 — The per-class, not per-object, wall

Does this compile? Explain precisely why.

class Account {
private:
    double balance;
public:
    bool richerThan(const Account& other) const {
        return balance > other.balance;   // (*)
    }
};
Recall Solution

Yes, it compiles. Line (*) reaches into other.balance, which is private — yet this is inside a member function of Account. The privacy wall is per-class, not per-object: any Account method may read the private members of any other Account.

Think of it as: the wall separates Account code from non-Account code, not one cookie from another cookie. Related tool: friend functions and classes extends this trust to chosen outsiders.

L3.2 — Why const blocks a call

Given double area() const { return w * h; } inside Rectangle, why does removing the const break this line, and what is the fix?

const Rectangle r = makeUnitSquare();
double s = r.area();   // want this to compile
Recall Solution

r is a const object — a promise nothing about it will change. When you call r.area(), the compiler must be sure area won't modify r. The only proof it accepts is the const keyword after the signature. Without it, the compiler cannot prove safety, so it refuses the call even though area genuinely changes nothing.

Fix: mark every read-only member const. For a unit square (), area() returns . See const correctness and this pointer (a const method receives a const this).


Level 4 — Synthesis

(Build a whole correct class from a spec.)

L4.1 — Temperature class

Write a class Temperature that:

  • stores Celsius internally, private;
  • has setC(double) that clamps any value below absolute zero () up to exactly ;
  • has getC() const and getF() const, where .

Then compute getF() after setC(-300); setC(25); and after setC(-300); alone.

Recall Solution
class Temperature {
private:
    double c = 0;
public:
    void setC(double v) {
        if (v < -273.15) v = -273.15;   // clamp at absolute zero
        c = v;
    }
    double getC() const { return c; }
    double getF() const { return c * 9.0 / 5.0 + 32; }
};

Trace AsetC(-300) clamps to , then setC(25) overwrites to : Trace BsetC(-300) alone clamps to : (This is absolute zero in Fahrenheit — a nice sanity check.) The clamp lives inside the only door to c, so no caller can ever store an impossible temperature.


Level 5 — Mastery

(Subtle interactions that separate fluent from shaky.)

L5.1 — protected across an inheritance chain

Predict compile/fail for each marked line.

class Shape {
protected:
    double area = 0;
private:
    int id = 7;
};
class Circle : public Shape {
public:
    void set() {
        area = 3.14;      // (1)
        id   = 9;         // (2)
    }
};
int main() {
    Circle c;
    c.set();
    double a = c.area;    // (3)
}
Recall Solution
  • (1) compilesarea is protected; Circle is a derived class → family access granted. ✅
  • (2) FAILSid is private in Shape. private is NOT inherited-accessible; even children can't touch it. ❌
  • (3) FAILSmain is outside; protected is invisible to the outside world just like private. ❌

The single distinction between private and protected is exactly line (1) vs. what would happen if area were private. See Inheritance in C++.

L5.2 — the aggregate final answer

Assuming lines (2) and (3) above were deleted so the file compiles, what value does c.area hold after c.set()? And what is id after construction?

Recall Solution
  • c.area (set by the derived method through the protected wall).
  • c.id (its in-class initialiser; the derived class never touched it, and never could).

So the object is — a clean demonstration that protected opens the family door while private keeps a member sealed even from its own children.


Connections