Worked examples — Classes — member functions, access specifiers (public, private, protected)
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.
- Sort each line by the wall it crosses.
depositandgetBalancesit underpublic:;balancesits underprivate:. Why this step? Access is decided purely by which specifier a name lives under, so classify the name first. - Line 1 & 2 touch public names from outside → allowed. ✅
Why? public = "everybody welcome," and
mainis everybody. - Line 3 & 4 touch
balancefrom outside → blocked at compile time. ❌ Why? private = "only my own member functions," andmainis not a member ofBankAccount.
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?
- Locate where the access happens. It happens inside
BankAccount::deposit, a member function ofBankAccount. Why this step? The wall depends on the location of the code, not on the field. - Apply the private rule. private = accessible inside the class's own member functions.
depositis 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?
- Identify the code's home class.
richerThanis a member function ofBankAccount. Why this step? The wall sits between classes, so the question is "is this code insideBankAccount?" — yes. - Apply per-class access. Because the code lives in
BankAccount, it may readbalanceof anyBankAccount, includingother. ✅ 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.

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.
- Line X touches
namefrom insideCircle, a child ofShape. Why this step? protected = private plus access from derived classes.Circlederives fromShape(: public Shape), so it's family — the blue arrow in the figure. ✅ - Line Y calls the public
label()frommain. public is open to everyone. ✅ Why?labelis underpublic:, so the outside world may call it. - Line Z touches
namedirectly frommain. Why this step?mainis 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?
- Note the exact specifier:
private, notprotected. Why this step? This single word decides everything; children getprotectedbut are shut out ofprivate. - Apply the child rule. A derived class may touch
protectedandpublicmembers of its base, but notprivateones.secretis 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?
- Read the invitation line.
friend void printBalance(...)inside the class says "I trust this function." Why this step?friendis the one legal way to poke a hole in the wall on purpose — the class must opt in. - 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;friendis 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?
ris aconstobject → it may only run methods that promise no change. Why this step? Aconstobject is read-only; letting a modifying method run would break that guarantee.area()isconst→ promise kept → line M is allowed. ✅ Why? It only readswandh, and it declared that promise.grow()is notconst→ it might modify → the compiler refuses on aconstobject → line N fails. ❌ Why? The compiler cannot provegrowleavesrunchanged (it doesn't — it writesw), 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.
set(3,4): both non-negative → stored →area() = 3*4 = 12. Why this step? Normal case, no wall of validation triggered.set(-5, 2):w<0is true → the guard fires →width=height=0→area() = 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. Becausewidth/heightare private, no outsider can bypass this guard.set(0, 9): zero is not negative, so it passes →width=0, height=9→area() = 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?
- Recall the single language difference. The only thing
structandclassdiffer in is the default access used when no specifier is written:structdefaults to public,classdefaults to private (see struct vs class). Why this step? Neither definition writespublic:orprivate:, so the keyword itself must supply the wall. This is the "zero-keyword" scenario — the default is the entire answer. - Classify
p.xby its wall.xhas no specifier and lives in astruct→ default public.mainis 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. - Classify
c.yby its wall.yhas no specifier and lives in aclass→ default private.mainis 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 madeyprivate 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)
owner(i.e.this->owner) from childSavings:ownerisprotected,Savingsis a derived class → OK. (covers E) Why this step?this->names this Savings object'sowner, and protected lets family touch it. - (2)
balance(i.e.this->balance) from childSavings:balanceisprivate→ children are shut out even viathis→ ERROR. (covers G) Why this step?this->doesn't add powers; the wall is still private, andSavingsis notAccount's own method. - (3)
a.read()on aconstobject:read()isconst, promise kept → OK. (covers I, A) Why this step? Aconstobject may call onlyconstmethods;readqualifies. - (4)
a.balancefrommain: private from outside → ERROR. (covers B) Why this step?mainis the outside world; private admits only the class's own methods. - (5)
s.rename()frommain:renameis public → OK. (covers A) Why this step? public welcomes everyone, so the call itself is fine (what it does inside was line 1). - (6)
s.ownerfrommain:owneris protected,mainis 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