5.2.5 · D3C++ Programming

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

3,578 words16 min readBack to topic

Before we begin, one promise: we use only ideas already built in the parent — class (a blueprint), object (one cookie stamped from that blueprint), member variable (data an object knows), member function (something an object does), and the three walls public / protected / private. Any new word (friend, this, const) is re-explained the moment it appears.


The scenario matrix

Think of every access question as "WHO is trying to touch WHAT, and is a wall in the way?" The rows below are every distinct situation this topic can produce. Each worked example is tagged with the cell(s) it covers.

# Cell (situation) Who touches it Allowed? Example
A public member from main (outside) outsider 1
B private member from main (outside) outsider ❌ (compile error) 1
C private member from the same class's own method the class itself 2
D one object reads another same-class object's private (per-class, not per-object) sibling object 3
E protected member from a derived (child) class family 4
F protected member from main (outside) outsider ❌ (compile error) 4
G private member of parent, from a child class child 5
H friend reaches into private invited outsider 6
I const object calling a non-const method read-only guard ❌ (compile error) 7
J degenerate / invalid input blocked by a public guard-door validation state stays legal 8
K struct default vs class default (the zero-keyword case) no specifier written struct→public, class→private 9
L exam twist: mixing all walls + this in one class + tracing what compiles you 10

We now walk every cell. Watch the tags like (covers A, B).


Worked examples

Example 1 — public works, private is blocked (covers A, B)

Forecast: guess which lines survive before reading on.

  1. Sort each line by the wall it crosses. deposit and getBalance sit under public:; balance sits under private:. Why this step? Access is decided purely by which specifier a name lives under, so classify the name first.
  2. Line 1 & 2 touch public names from outside → allowed.Why? public = "everybody welcome," and main is everybody.
  3. Line 3 & 4 touch balance from outside → blocked at compile time.Why? private = "only my own member functions," and main is not a member of BankAccount.

Verify: count the public accesses = 2, count the private-from-outside accesses = 2. The compiler is a bouncer at compile time, so illegal lines never run. ✔


Example 2 — the class touches its own private (covers C)

Forecast: does "private" block the class from using its own field?

  1. Locate where the access happens. It happens inside BankAccount::deposit, a member function of BankAccount. Why this step? The wall depends on the location of the code, not on the field.
  2. Apply the private rule. private = accessible inside the class's own member functions. deposit is exactly that. ✅ Why? The whole point of private is that the class's own doors (public methods) may freely touch the hidden data — that's how a guarded gateway works.

Verify: run acc.deposit(100) then acc.getBalance() → returns 100. The private write happened through a public door. ✔


Example 3 — one object peeks at another (covers D, the "per-class not per-object" trap)

Forecast: other.balance reads a different object's private field. Blocked or allowed?

  1. Identify the code's home class. richerThan is a member function of BankAccount. Why this step? The wall sits between classes, so the question is "is this code inside BankAccount?" — yes.
  2. Apply per-class access. Because the code lives in BankAccount, it may read balance of any BankAccount, including other. ✅ Why? private means "private to the class," not "private to the individual object." The compiler trusts the whole class with the whole class's data.

Verify: with a=500, b=300: is true. Swap to a=100,b=999: is false. ✔


Example 4 — protected from a child vs from outside (covers E, F)

Now the middle wall. The figure below shows the idea as three nested rooms: the outer room is main (the outside world), the dashed inner room is the family of classes (Shape and its child Circle), and the pink box in the middle is the protected field name. Notice how the blue arrow starting inside the family room reaches the box freely, while the yellow arrow coming from the outer room is stopped by a pink cross — that cross is the compile error.

Figure — Classes — member functions, access specifiers (public, private, protected)
Figure: protected is reachable from the family room (blue arrow, Circle::label() → OK) but not from the outer world room (yellow arrow, c.name in main → BLOCKED). The wall is invisible to family, solid to strangers.

Forecast: guess which of the three lines the compiler rejects.

  1. Line X touches name from inside Circle, a child of Shape. Why this step? protected = private plus access from derived classes. Circle derives from Shape (: public Shape), so it's family — the blue arrow in the figure. ✅
  2. Line Y calls the public label() from main. public is open to everyone. ✅ Why? label is under public:, so the outside world may call it.
  3. Line Z touches name directly from main. Why this step? main is not a member and not a derived class — it is the outside world (the yellow arrow), and protected blocks the outside world exactly like private does. ❌

Verify: trace: c.label() sets name="circle" legally; reading it from main needs a public getter. Direct outside write (line Z) fails. ✔


Example 5 — a child cannot see the parent's private (covers G)

Forecast: children inherit — but do they see private?

  1. Note the exact specifier: private, not protected. Why this step? This single word decides everything; children get protected but are shut out of private.
  2. Apply the child rule. A derived class may touch protected and public members of its base, but not private ones. secret is private → line P is blocked. ❌ Why? private literally means "no one but the base class itself." Children are family, but private is personal — even family can't open it.

Verify: contrast with Example 4: there name was protected and the child could touch it; here secret is private and the child cannot. The one-word swap flips the result. ✔


Example 6 — friend is an invited outsider (covers H)

Forecast: printBalance is not a member — normally blocked. But the class named it a friend. Now?

  1. Read the invitation line. friend void printBalance(...) inside the class says "I trust this function." Why this step? friend is the one legal way to poke a hole in the wall on purpose — the class must opt in.
  2. Apply the friend rule. A friend may read private/protected exactly as if it were a member. Line F reads balance → allowed. ✅ Why? Encapsulation means the class decides who gets in; friend is that decision made explicit and deliberate (see friend functions and classes).

Verify: balance = 250, so the output is exactly 250. Remove the friend declaration → line F becomes illegal (private from a non-member). ✔


Example 7 — const object cannot call a non-const method (covers I)

Forecast: r is const. Which call does the compiler reject?

  1. r is a const object → it may only run methods that promise no change. Why this step? A const object is read-only; letting a modifying method run would break that guarantee.
  2. area() is const → promise kept → line M is allowed.Why? It only reads w and h, and it declared that promise.
  3. grow() is not const → it might modify → the compiler refuses on a const object → line N fails.Why? The compiler cannot prove grow leaves r unchanged (it doesn't — it writes w), so it blocks the call. See const correctness.

Verify: . A const object can only call const methods; drop the const on r and both lines would compile. ✔


Example 8 — degenerate input blocked by the guard-door (covers J)

Forecast: predict the three area values, especially the negative and the zero cases.

  1. set(3,4): both non-negative → stored → area() = 3*4 = 12. Why this step? Normal case, no wall of validation triggered.
  2. set(-5, 2): w<0 is true → the guard fires → width=height=0area() = 0. Why this step? This is the degenerate/invalid cell — a negative width is an impossible rectangle, so the only door refuses it and resets to a legal zero state. Because width/height are private, no outsider can bypass this guard.
  3. set(0, 9): zero is not negative, so it passes → width=0, height=9area() = 0*9 = 0. Why this step? This is the zero-input limiting case: a valid but flat rectangle. Area is genuinely , and that's fine — zero is a legal state, negative is not.

Verify: ; rejected → ; flat → . Units: area = length×length, all consistent. Note cells 2 and 3 give the same number for different reasons — one is a rejection, one is a legal flat shape. ✔


Example 9 — the only difference: struct vs class default (covers K)

Forecast: same code shape, one is struct, one is class. Which access fails?

  1. Recall the single language difference. The only thing struct and class differ in is the default access used when no specifier is written: struct defaults to public, class defaults to private (see struct vs class). Why this step? Neither definition writes public: or private:, so the keyword itself must supply the wall. This is the "zero-keyword" scenario — the default is the entire answer.
  2. Classify p.x by its wall. x has no specifier and lives in a struct → default public. main is the outside world, and public welcomes everyone → line 1 compiles. ✅ Why this step? Same "who touches what" question as Example 1 — an outsider reaching a public member is always allowed.
  3. Classify c.y by its wall. y has no specifier and lives in a class → default private. main is the outside world, and private admits only the class's own methods → line 2 is blocked. ❌ Why this step? An outsider reaching a private member is the exact cell B failure — the default just made y private without you typing it.

Verify: if you change class C to struct C, line 2 would compile too; if you change struct P to class P, line 1 would fail. The default flips with the keyword. ✔


Example 10 — the exam twist: trace everything at once (covers L, and re-hits A–I; introduces this)

First, the one new word left to re-explain. Every member function secretly receives a hidden pointer named ==this==, which points to the exact object the function was called on. When you write a bare member name like balance inside a method, the compiler silently reads it as this->balance — "the balance belonging to the object I was called on." So this is just the answer to "which object's data am I touching right now?" It never changes what you may touch — only which object's copy you touch (see this pointer).

Forecast: predict all six OK/ERROR before scoring.

  1. (1) owner (i.e. this->owner) from child Savings: owner is protected, Savings is a derived class → OK. (covers E) Why this step? this-> names this Savings object's owner, and protected lets family touch it.
  2. (2) balance (i.e. this->balance) from child Savings: balance is private → children are shut out even via thisERROR. (covers G) Why this step? this-> doesn't add powers; the wall is still private, and Savings is not Account's own method.
  3. (3) a.read() on a const object: read() is const, promise kept → OK. (covers I, A) Why this step? A const object may call only const methods; read qualifies.
  4. (4) a.balance from main: private from outside → ERROR. (covers B) Why this step? main is the outside world; private admits only the class's own methods.
  5. (5) s.rename() from main: rename is public → OK. (covers A) Why this step? public welcomes everyone, so the call itself is fine (what it does inside was line 1).
  6. (6) s.owner from main: owner is protected, main is outside the family → ERROR. (covers F) Why this step? protected blocks the outside world exactly like private.

Verify: three OK, three ERROR. Each error is a wall doing its job: (2) private hides from child, (4) private hides from outside, (6) protected hides from outside. The const object (3) succeeds only because read() promised no change, and this-> never widens access — it only names which object. ✔


The 80/20 recap


Connections

  • Parent topic
  • Encapsulation & Data Hiding — the why behind every wall here
  • Inheritance in C++ — makes protected (cells E, F, G) matter
  • friend functions and classes — cell H
  • const correctness — cell I
  • this pointer — the hidden argument in cells C, D, and Example 10
  • struct vs class — cell K
  • Constructors and Destructors