---
title: 'Classes and Access Control'
source: 'https://academia.sh/en/courses/cpp-fundamentals/classes-and-access-control'
course: 'C++ Fundamentals and Object Design'
language: en
updated: '2026-09-26T03:02:45+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - "Confirms that a member function's this points at the object it was called on"
    - 'Applies the default access difference between class and struct, and public/private/protected'
    - 'Confirms by running it that access control operates at the level of the class, not the object'
---

# Classes and Access Control

Measures which object a member function works with, and that access control operates at the level of the class, not the object.

The previous lesson measured which object a free function works with by looking at its
parameter: `shift(Point n)` worked with its own copy, `nudge(Point* p)` reached the
caller's object through an address. A member function also has a parameter, but the
compiler passes it implicitly: the address of the object the call was made on. This
implicit parameter's name is `this`, and it is available throughout a member function's
body — present even when not written. This lesson measures that same implicit parameter
with the same core instrument: when a member function is called, which object does
`this` point at, and how does that fact meet access control?

## Classes and Access Control

`class` and `struct` are the same thing in C++; both define a type that can carry data
members and member functions, both can be inherited from, both run a constructor and a
destructor. The only difference between them is the default access level. A `struct`'s
members are `public` by default, a `class`'s members are `private` by default — no other
behavioral difference exists. This is an inheritance from C: in C, a `struct` was only a
data-carrying record, it carried no functions; when C++ gave `struct` the ability to
carry member functions, it kept the default access `public` for backward compatibility.
Using `struct` for types that mostly carry data with little behavior, and `class` for
types whose behavior is protected by access control, is a convention in a C++ codebase —
the language itself does not enforce it.

A class declares three access levels:

- `public` — reachable from outside the class, from any code, anywhere. A class's
  outward-facing **interface** lives here.
- `private` — reachable only from the class's own member functions (and from functions
  or classes declared as `friend`). The data members that hold a class's own internal
  state are usually here.
- `protected` — reachable from the class's own member functions and from derived
  classes; its difference from `private` only shows up under inheritance. Inheritance
  is not used in this course until M11/K04's second topic, so `protected` is only named
  here.

An access-level declaration (`public:`, `private:`, or `protected:`) applies, inside a
class body, to every member after that point, up to the next access-level declaration.
In the `Account` class, the three member functions under `public:` are callable from
outside; `balance_` under `private:` is not — the same class body can also contain more
than one `public:`/`private:` block, and the compiler merges them all.

A class carries **data members** (a variable with its own copy in every instance of the
object — `balance_` carries its own value in every `Account` object) and **member
functions** (code shared across all instances of the class, reaching the called
instance's data members through `this` — `deposit`'s machine code is generated once, not
separately for every `Account` object). If a member function is marked `const`, `this`
itself is `const`: the function cannot modify data members, only read them. `balance()`
and `equals` are `const` for this reason — both read `balance_` without modifying it;
`deposit` is not `const` because it modifies `balance_`. Only `const` member functions
can be called on a `const` object; calling a non-`const` member function on a `const`
object is a compile error, because the compiler cannot guarantee that `this` will not be
modified inside the function.

A `friend` declaration grants a function or class, which is not itself a member, access
to a class's `private` members — the sole exception to access control, and one granted
explicitly by the class's own declaration, never taken from outside. In this course,
`friend` is used exactly once, so that `operator<<` can read a class's `private` data
member (in the Operator Overloading lesson); here it is only named.

Encapsulation — hiding a class's internal state from the outside and exposing access
only through functions — was already covered in M08; it is not retaught here, only
applied through C++'s `public`/`private` syntax. `Account` applies this idea: `balance_`
cannot be written to directly from outside, it can only be changed through `deposit`.

## A Member Function's this

`this` is an implicitly defined pointer inside a member function's body; its type is
determined by the class it was called on. Inside `Account::deposit`, `this`'s type is
`Account* const` — the pointer itself cannot be changed (it cannot start pointing at a
different `Account`), but the object it points at can be modified. Inside a `const`
member function — such as `Account::balance() const` — `this`'s type is `const Account*
const`: neither the pointer nor the object it points at can be modified, which is why a
`const` member function only gains read access to data members. Code does not write this
pointer by hand — `balance_ += amount` is read by the compiler as `this->balance_ +=
amount` — but the measurement below shows plainly that `this` really does point at the
called object, not some other object: the address `&h` captured before the call to
`h.deposit(50)` is compared against the equivalent of `this` used implicitly inside the
function.

```cpp
// account.hpp — an account whose balance is private
#ifndef ACCOUNT_HPP
#define ACCOUNT_HPP

class Account {
public:
    void deposit(int amount) { balance_ += amount; }
    int balance() const { return balance_; }
    bool equals(const Account& other) const { return balance_ == other.balance_; }

private:
    int balance_ = 0;
};

#endif
```

```cpp
// this-measurement.cpp — does the member function's this point at the called object
#include <cstdio>
#include "account.hpp"

int main() {
    Account a;
    Account* before = &a;

    a.deposit(50);
    std::printf("does this point at the called object %s\n", before == &a ? "yes" : "no");
    std::printf("balance %d\n", a.balance());
    return 0;
}
```

```text
does this point at the called object yes
balance 50
```

**CR3.** The compiler passes `&a` as `this` in the call `a.deposit(50)`; the line
`balance_ += amount` inside `deposit` modifies `a.balance_`, no other `Account`. This
expression is of kind **self**: it works with `a` itself.

## Access Control Asks About the Class, Not the Object

`balance_` is `private`; it cannot be written from outside `Account` — a line like that
attempted inside `main` is rejected by the compiler, because `main` is not a member of
the `Account` class. But `Account`'s own member function can reach directly into
**another** `Account` object's `balance_` — access control asks about the class, not the
object. `equals` shows this: `other.balance_` is not the caller's own `balance_`, but it
compiles because it belongs to the same class. This is a C++ rule that does not hold in
many languages — in some languages access control works at the object level and an
object can only touch its own data; in C++ the boundary is the class itself, not a
single object.

```cpp
// equals-measurement.cpp — a member function reaching another object's private member
#include <cstdio>
#include "account.hpp"

int main() {
    Account a;
    Account b;
    a.deposit(30);
    b.deposit(30);

    std::printf("are a and b equal %s\n", a.equals(b) ? "yes" : "no");

    b.deposit(1);
    std::printf("are a and b equal %s\n", a.equals(b) ? "yes" : "no");
    return 0;
}
```

```text
are a and b equal yes
are a and b equal no
```

**CR4.** The access `other.balance_` inside `a.equals(b)` reaches `b`'s `private` member
from `a`'s member function; the compiler accepts this only by looking at `other`'s type
(`const Account&`), not at whether `a` and `b` happen to be the same object. The access
decision is made **at compile time**, from the class information.

This is one of the points where many C++ learners are first surprised: `private` is
assumed to mean "only this object can access its own data," but the actual rule is
"only this **class**'s member functions can access it." While `equals`'s body is
compiled, the compiler only knows this: `other` is a `const Account&`, and the function
currently being compiled is a member of the `Account` class; these two facts are enough
to permit the access `other.balance_`. Whether `a` and `other` (that is, `b`) are the
same runtime object is not known at compile time and plays no part in the access
decision at all — indeed, in the second call `a` and `b` are separate objects and the
access still compiles.

## Why private Protects the Class, Not the Object

This rule is not arbitrary; it makes it possible for a class's author to protect its
**invariants** (a condition that must always hold for an object — for example, that
`balance_` must never drop below zero). If `private` only meant "reachable from your own
object, not from other objects," functions like `equals` that compare two objects, or
functions that copy data from one object into another, could not be written — every
member function could only look at its own `this`. Class-level access lets a class's own
member functions safely share data among themselves, while closing that same data off to
code outside the class. The boundary it draws is not the object — it is **the
declaration in the class's own source file.**

The opposite of this is a struct with an open field: in a record like that, calling code
can change that field without passing through any check at all. `Account` does not make
that mistake — there is no path to write `balance_` from outside, it can only be changed
through `deposit`. `deposit` today does nothing beyond `balance_ += amount`, but because
of `private`, a rule could later be added — rejecting a negative `amount`, or enforcing
an upper bound — and that rule could be written into `deposit` alone, without changing
any caller, because the **only** path to `balance_` is `deposit`. This is the real
engineering value `private` buys: the freedom to change the class's internal
representation without touching the code outside it.

## Choosing Which Call a const Object Allows

The question of why `balance()` and `equals` are marked `const` while `deposit` is not
has a practical consequence: a function taking a `const Account&` parameter can only
call `const` member functions through that parameter. `equals`'s signature is `bool
equals(const Account& other) const`; its body accesses `other.balance_` directly, but it
would also compile if it called `other.balance()` instead — because `balance()` is
`const` and a `const` function can be called through a `const` reference. Had
`other.deposit(10)` been attempted, it would not compile: `deposit` is not `const`, and
a non-`const` member function cannot be called on a `const` object (or on the object a
`const` reference points at). This rule shows that writing `const` in a function's
signature is not merely a documentary note — it is a contract the compiler actually
enforces: that `equals` will not modify `other` is verified at compile time from the
function's own signature.

The same rule applies to `this`. Because `Hesap::equals` — `Account::equals` — is marked
`const`, its own `this` is also `const Account* const`; if a change such as
`balance_ += 1` were attempted inside this function's body — even on its own object —
the compiler rejects it. A `const` member function can modify neither its own object nor
another object it received by `const` reference; the only things it can modify are local
variables and data members marked `mutable` (`mutable` is a marker reserved for rare
cases such as caching, and it is not used in this course).

## Declaration Order and Access-Level Blocks

In the `Account` class, the access-level declarations follow a particular layout order:
first the member functions exposed to the outside under `public:`, then the data member
under `private:`. This order is not a rule of the language — the compiler would produce
the same result if `private:` came first and `public:` came after — but it is a common
habit for readability: someone reading a class first sees the interface it offers, then
the internal representation. The reverse order (`private` data first, `public` functions
after) is equally valid and preferred in some codebases; this lesson follows the
`public`-first order and stays consistent with it for the rest of the course.

An access-level declaration is not required to be used only once — `public:` and
`private:` can each repeat more than once within the same class body, each covering the
block that follows it. `Account` does not do this; because the class is small, a single
`public:` and a single `private:` are enough. In a larger class, using more than one
block to keep related members together — a separate `public:` for constructors, a
separate `public:` for query functions, for example — can improve readability; this is a
style choice, it does not change the compiler's behavior.

## The Bounding Measurement

The expression this lesson measures is of kind **self**: `h.deposit(50)` writes to `h`
itself. But the boundary of access control is visible here too: "who accesses" a
`private` member is asked at the level of the class, not the object. The call to
`other.balance_` inside `equals` proves this — a `private` member of a different object
of the same class can be reached, without that object's own function, from just another
member function of the same class. `private` protects the class's boundary, not the
object.

This boundary has a further consequence: access to a `private` member is possible from
every member function in the class's **own** source code — the number of such member
functions can grow as the class grows, and each one carries the same risk of breaking
the same invariant. `Account` is small, with three member functions, and all of them are
visible in this lesson; a large class can have dozens of member functions that reach
`private` data, and the rule measured in this lesson — that access is tied to the class,
not the object — remains unchanged at that scale too. `private` answers the question "how
many places can reach this" not with a count of objects, but with the class's count of
member functions.

These two points — which object a given expression works with, and that access control
asks about the class, not the object — together explain why a class can draw a reliable
boundary. `this` points at the correct object on every call, so a member function never
confuses its own object with another; `private` grants every member function access to
every object of the same class, so the class can write operations that require sharing
data internally (comparison, copying, merging). These two rules together lay the ground
for the next lesson's subject, the constructor: a constructor is also a member function,
it has its own `this`, and it reaches `private` data members with the same freedom — but
what sets it apart is that the object it is called on does not yet exist.

## Summary

- The expression this lesson measures is of kind **self**: in the call `h.deposit(50)`,
  `this` equals `&h`.
- The only difference between `class` and `struct` is the default access level;
  `public`/`private`/`protected` declare three access levels.
- Inside a `const` member function, `this` is itself `const`; data members cannot be
  modified.
- Access control operates **at compile time** and **at the level of the class**: a
  member function can reach a `private` member of a different object of the same class.
- Encapsulation was defined in M08; here it was only applied through `class`/`private`
  syntax.

## Next Step

This lesson showed which object a member function works with; the next lesson asks the
same question of a **constructor** — it measures that an argument implicitly built from
an `int` is an unnamed, temporary object, and exactly when that object ceases to exist.
