5.2.5 · D2C++ Programming

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

2,328 words11 min readBack to topic

Step 1 — Start with no walls at all

WHAT. Before we have a class, imagine data floating around loose. A rectangle is just two numbers: a width and a height. Nothing guards them.

WHY. To understand why we build walls, we must first feel the pain of having none. A number with no wall around it can be set to anything — including nonsense.

PICTURE. Look at the figure. Two amber boxes hold the values width = 3 and height = 4. There is no border, no gate — arrows come at them from every direction. Any line of code anywhere can write into them.

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

Here the only symbol is the plain variable name width. It means "a labelled box in memory holding a number." No new notation yet — we earn everything from here.


Step 2 — Draw a box around the data (the class)

WHAT. We put the two numbers inside a container and give the container a name: Rectangle. That container is a class.

WHY. You cannot guard things that are scattered. First you gather them into one bounded region — then you can put a single gate on that region. The box is the precondition for the wall.

PICTURE. The same two amber value-boxes, now surrounded by a single cyan rectangle labelled class Rectangle. The outside world is the blueprint-blue area beyond the border.

Figure — Classes — member functions, access specifiers (public, private, protected)
class Rectangle {
    double width, height;   // now they live INSIDE the box
};

Symbol check: class Rectangle { ... };class is the keyword "start a blueprint," Rectangle is the name we chose, { } fences the region, and the trailing ; closes the declaration. All plain words, all just introduced.


Step 3 — Seal the data: the private wall

WHAT. We mark the data private. This turns the box's border into a solid wall for those two numbers — no outside line may read or write them.

WHY. private answers the question "who is allowed to touch this box directly?" with the strictest possible answer: only code written inside this same class. Nobody outside. That is what makes width = -5; from main impossible.

PICTURE. The border is now a thick white wall. An arrow from the outside (main) slams into the wall and bounces off, drawn in amber and marked ❌. An arrow from inside the class reaches the data freely, marked ✅ cyan.

Figure — Classes — member functions, access specifiers (public, private, protected)
class Rectangle {
private:                    // <-- everything below is walled off
    double width, height;
};
  • private: — the keyword private followed by a colon. Read it as "from here down, insiders only."
  • The wall is checked at compile time. The compiler is the bouncer; an illegal line never even becomes a running program.
Recall Who counts as "inside"?

Which code can cross a private wall? ::: Only the class's own member functions (and any declared friends). Nothing from main, nothing from a child class.


Step 4 — Cut a gate: public member functions

WHAT. A fully sealed box is useless — you can never put a rectangle in or read its area out. So we cut a controlled gate into the wall: a public member function.

WHY. public answers "who may touch this?" with everyone. But — and this is the trick — we make only the functions public, never the raw data. The outside world talks to the object through the gate, and the gate can inspect what passes.

PICTURE. The white private wall now has one cyan doorway labelled setDimensions(). The outside arrow no longer bounces — it goes through the door. But notice the door has a guard box on it (amber): the validation check.

Figure — Classes — member functions, access specifiers (public, private, protected)
class Rectangle {
private:
    double width, height;
public:                                     // <-- gate opens here
    void setDimensions(double w, double h) {
        if (w < 0 || h < 0) { width = height = 0; return; }  // the guard
        width = w; height = h;              // insider: may write freely
    }
};

Term by term inside the gate:

  • void setDimensions(double w, double h) — a function taking two incoming numbers w, h; void means it hands nothing back.
  • if (w < 0 || h < 0) — the guard. || means "or." If either input is negative, we refuse it.
  • width = w; — because this code lives inside the class, it is an insider and may cross the private wall to write the data.

Step 5 — A read-only gate: the const member function

WHAT. Some gates only read the data and never change it — like asking "what's your area?" We mark those const.

WHY. const answers "does calling this function alter the object?" with a promise: no. Why bother promising? Because a promise the compiler enforces lets you safely call the function on objects that are themselves declared unchangeable (const Rectangle r;).

PICTURE. A second doorway labelled area() const, drawn as a one-way window: data flows out (cyan arrow leaving) but the door is barred against writing in (amber bar, ❌).

Figure — Classes — member functions, access specifiers (public, private, protected)
double area() const { return width * height; }
//                ^^^^^ promise: "I will not modify the object"
  • area() — takes no inputs, hands back one number.
  • const after the parentheses — the read-only seal. If you tried to write width = 0; inside a const function, the compiler would reject it.
  • return width * height; — reads the two private members (legal — it is an insider) and multiplies them.

See const correctness for the full story on why this promise matters across a whole codebase.


Step 6 — Two objects, one blueprint (per-class, not per-object)

WHAT. The class is a stamp; each Rectangle you create is a fresh cookie with its own width/height. But the wall is a property of the class, not of each individual object.

WHY. This is the most commonly misunderstood point. "Private" sounds like "secret from everyone including other rectangles." Not so. A Rectangle method may reach into another Rectangle's private data — because both objects are the same class, and the wall stands between classes, not between instances.

PICTURE. Two identical walled boxes, r1 and r2, side by side. A method running inside r1 sends a cyan arrow across into r2's private data — allowed (✅), because both are Rectangle. An arrow from generic outside code still bounces off both (❌).

Figure — Classes — member functions, access specifiers (public, private, protected)
bool widerThan(const Rectangle& other) const {
    return width > other.width;   // ✅ reaches other's private width — same class!
}
Recall The per-class rule

Is private access per-object or per-class? ::: Per-class. One Rectangle object's member function can read another Rectangle's private members, because the wall separates classes, not instances.


Step 7 — The middle wall: protected and the family

WHAT. Now a new character arrives: a child class (via Inheritance in C++). A child is family — it should inherit and use the internal machinery, but the outside world still must not. private is too strict for family (it locks children out); public is too loose (it opens the data to everyone). We need a third, middle wall: protected.

WHY. With only two levels you cannot express "insiders and their children — but not the public." protected is exactly that in-between answer to "who may touch this?": the class itself and any class that inherits from it.

PICTURE. Three concentric zones. The innermost solid wall (private) admits only the class. The middle dashed wall (protected) admits the class plus a child box drawn overlapping it. The outermost region (public / world) is still shut out of both inner walls.

Figure — Classes — member functions, access specifiers (public, private, protected)
class Shape {
protected:
    string name;                    // family may touch, outsiders may not
};
class Circle : public Shape {
public:
    void label() { name = "circle"; }   // ✅ child reaches protected member
};
// main:  Circle c;  c.name = "x";       // ❌ still walled off from outside

Reading the wall table by column — every case covered:

Specifier Same class Child class Outside (main)
public
protected
private

See Encapsulation & Data Hiding for why we default to the strictest wall and open up only when forced to.


Step 8 — The degenerate cases (nothing left unshown)

We must cover the corners so you never hit a surprise:

Case A — no specifier written at all. The default depends on the keyword. For class the default is private; for struct it is public. That single difference is the only language-level distinction between them — see struct vs class.

class C { int x; };   // x is PRIVATE  (class default)
struct S { int x; };  // x is PUBLIC   (struct default)

Case B — everything public. A class with no walls at all behaves like a loose bag of variables — you are back at Step 1, with all its dangers. Legal, but it throws away encapsulation.

Case C — everything private, no gates. A class with private data and no public functions is a sealed box you can create but never use or read. Legal, but pointless — you need at least one gate.

Case D — a friend. A friend is an outsider you personally invite past the private wall. It is the deliberate exception to Step 3's rule, used sparingly.

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

The figure lines up all four corner cases: the class/struct default split, the all-public bag, the sealed useless box, and the friend who is handed a key.


The one-picture summary

Everything on one blueprint: the data locked behind three nested walls, the public gates (one read-write with a guard, one read-only), the family child sharing the protected middle zone, and the outside world stopped at the outermost wall.

Figure — Classes — member functions, access specifiers (public, private, protected)
Recall Feynman: tell it back in plain words

Start with two loose numbers — a width and a height — sitting in the open where any careless line could set the width to minus five. That's a bug waiting to happen. So we do four things in order. One: gather the numbers into a named box called a class. Two: brick up the box with private, so nobody outside can poke the numbers. Three: cut a gate — a public function — and put a guard on it that rejects nonsense, so the only way to change the data is through a check. Four: for reading, we cut a one-way window, const, that lets facts flow out but never lets anything in.

Then two subtleties. The wall stands between classes, not objects — so one rectangle can peek at another rectangle's private numbers, because they're the same kind of thing. And when a child class inherits, private would lock the family out and public would let strangers in, so we invent a middle wall, protected: family welcome, strangers blocked. Corner cases: class defaults to walled, struct defaults to open, and a friend is a stranger you personally hand a key. That's the whole idea — data hidden behind guarded gates, opened exactly as much as needed and no more.


Connections

  • 5.2.05 Classes — member functions, access specifiers (public, private, protected) (Hinglish) — the parent topic
  • Encapsulation & Data Hiding
  • Constructors and Destructors
  • Inheritance in C++ — where the protected middle wall earns its keep
  • const correctness
  • friend functions and classes
  • this pointer
  • struct vs class