C++ / CLASSES AND OBJECT LIFECYCLE
Classes, objects, and access specifiers
Define a class with private data and a public interface, and predict exactly which code may name each member, including another object of the same class.
What you will learn
- Declare data private and expose behaviour through named public member functions
- Remember that class defaults to private access while struct defaults to public
- Read another object's private members from inside a member function of that class
- Enforce a range invariant inside one setter instead of trusting every caller
Understanding Classes, objects, and access specifiers
A class is a description of a type: a list of data members saying what each object holds, plus member functions saying what can be done with them. When you write `Thermostat hall;` the compiler lays out storage for that object's own data members only, because the member functions exist once in the program and are told which object to work on. So two Thermostat objects have two independent `target_` values, and `hall.setTarget(21.5)` changes nothing about `attic`.
`public:`, `private:` and `protected:` are labels, not blocks: each one applies to every declaration after it until the next label, and they may repeat in any order. The check they drive happens entirely at compile time and depends on where the code naming the member sits, not on which object is involved, which is why `warmerThan` may read `other.target_`. Access is granted per class, not per object; `protected` widens that circle to classes derived from this one.
Making data private is what lets you state a rule once and rely on it everywhere else. `setTarget` clamps to 5 through 30 degrees, so no Thermostat in the program can hold 45; if `target_` were public, every assignment in every file becomes a place where that rule can be broken. The same boundary lets you rename `target_`, or store tenths of a degree in an int instead, without touching a single caller, because callers only ever named the public functions.
<iostream>
class Thermostat {
public:
void setTarget(double celsius) {
if (celsius < 5.0) celsius = 5.0;
if (celsius > 30.0) celsius = 30.0;
target_ = celsius;
}
double target() const { return target_; }
// A member of Thermostat may read another Thermostat's private data.
bool warmerThan(const Thermostat& other) const {
return target_ > other.target_;
}
private:
double target_ = 18.0; // only the functions above may touch this
};
int main() {
Thermostat hall;
Thermostat attic;
hall.setTarget(21.5);
attic.setTarget(-10.0); // clamped by setTarget, not by the caller
std::cout << "hall: " << hall.target() << '\n';
std::cout << "attic: " << attic.target() << '\n';
std::cout << "hall warmer than attic? " << std::boolalpha
<< hall.warmerThan(attic) << '\n';
// hall.target_ = 99.0; // error: 'target_' is a private member
}
Access specifiers put a compile-time boundary around a class's data, and the compiler decides who may name a member from where the code sits, not from which object it touches.
Worked examples
class versus struct defaults
Shows that the only access difference between class and struct is what applies before the first label.
<iostream>
struct Point { // struct: public unless stated otherwise
int x = 0;
int y = 0;
};
class Tag { // class: private unless stated otherwise
int id_ = 7; // no label above it, so this is private
public:
int id() const { return id_; }
};
int main() {
Point p;
p.x = 3; // allowed, x is public
p.y = 4;
Tag t;
// t.id_ = 9; // error: 'id_' is a private member of 'Tag'
std::cout << p.x << ',' << p.y << " id=" << t.id() << '\n';
}
Example explained
Line 1`int id_ = 7;` sits above any label inside a class, so it is private and `main` cannot name it.
Line 2`int id() const { return id_; }` follows `public:`, so it is the only route from `main` to that value.
Line 3Uncommenting `t.id_ = 9;` fails to compile even though `t` is a local variable of `main`; owning the object grants nothing.
Line 4`p.x = 3;` compiles because Point's members are public, which also means Point enforces no rule about x at all.
friend as a deliberate exception
Demonstrates a class granting one outside function permission to read its private data.
<iostream>
class Wallet {
public:
void deposit(int cents) { cents_ += cents; }
private:
int cents_ = 0;
friend void audit(const Wallet& w);
};
void audit(const Wallet& w) {
std::cout << "balance in cents: " << w.cents_ << '\n';
}
int main() {
Wallet w;
w.deposit(250);
w.deposit(99);
audit(w);
}
Example explained
Line 1`friend void audit(const Wallet& w);` names an outside function that may read private members; it does not make `audit` a member.
Line 2`audit` is a free function, so it is called as `audit(w)` and receives the Wallet as an ordinary parameter.
Line 3The friend declaration would behave identically in the public section, because only the class can grant friendship and the labels do not apply to it.
Line 4`w.cents_` inside `audit` compiles solely because of that grant; delete the friend line and the same expression becomes an error.
Important notes
`private` is a compile-time rule about who may spell a name, not memory protection: the member still occupies space in every object, and a cast or a debugger still reaches the bytes.
The `= 18.0` on `target_` is a default member initialiser that keeps a private member valid before any setter runs; constructors, covered separately, let you demand a value at creation time.
Common mistakes
Omitting `public:` in a `class`, so every function is private and `main` fails with 'is private within this context'; switching `class` to `struct` makes it compile but also exposes the data you meant to protect.
Writing a plain getter and setter for every private member with no checks inside them, which is public data with extra typing: the guarantee the setter was supposed to provide no longer exists.
Forgetting the semicolon after the class's closing brace, which produces a cascade of errors on the lines following the class rather than on the class itself.
Try it yourself
Change, predict, then run
Write a class `Grade` with a private `int score_ = 0`, a public `setScore(int)` that clamps to 0 through 100, a public `score()`, and a public `beats(const Grade& other)` that compares `score_` with `other.score_` directly. Create two grades, feed one 130 and the other 72, and print both scores plus the result of `beats`.
Open the C++ workspaceCheck your understanding
Inside a member function of Thermostat, why does `other.target_` compile when `other` is a different Thermostat object and `target_` is private?
- Access is granted per class, so code inside any Thermostat member may name the private members of any Thermostat object
- Because `other` was passed by reference, and reference parameters are not subject to access checking
- Because both objects share a single copy of `target_`, so there is no privacy boundary between them
- Because the compiler treats `private` as `protected` while compiling the body of a member function
Show answer
Privacy attaches to the name in the class, and the compiler asks only where the code naming it appears, so any Thermostat member function passes the check for every Thermostat object. Option three is tempting but wrong: each object owns its own `target_`, and sharing one copy across all objects would require a static member, which is a different mechanism entirely.