This module is your on-ramp to object-oriented programming (OOP) in modern C++ and, in particular, to the idea of encapsulation: designing a type so its data stays consistent while callers interact through a clear, stable interface.
In this module, you will learn:
public, private, protected) and why “private by default” matters.At the end of the module, you’ll have a chance to take a quiz to confirm you can spot good encapsulation and recognize common mistakes (like leaking invariants through public data).
OOP in C++ is not “getters and setters everywhere.” It’s a set of design practices that emphasize modeling (types represent real concepts), responsibility (a type owns its invariants), and interfaces (callers use behavior, not internal representation).
Encapsulation is the keystone because it lets you change implementation details without forcing changes onto every caller. When the internal representation is hidden (or at least not relied upon), you can evolve a class safely: add caching, switch containers, change validation rules, or tighten invariants—without breaking the rest of your program.
You’ll often hear these used interchangeably, but in practice they describe two sides of the same goal:
In C++, information hiding is typically achieved with private data members, carefully chosen public member
functions, and sometimes by using the Pimpl idiom when you want stronger ABI stability.
A well-encapsulated class protects its invariants—conditions that must always be true for the object to be “valid.” Instead of letting callers write directly to member fields, you provide operations that keep those invariants intact.
In C++23 terms, think in “contracts” even though language-level Contracts are still evolving:
design your public member functions so they enforce valid state transitions. Validation doesn’t have to mean verbose
setters; often the best approach is to offer domain operations (e.g., deposit(), withdraw())
rather than “set arbitrary fields.”
Here’s a minimal class that demonstrates the core idea: state is private, and callers use behavior. Notice how the public interface prevents invalid states and keeps the class easy to evolve.
#include <iostream>
class Car {
private:
int speed_{0}; // invariant: speed_ >= 0
public:
void setSpeed(int s) {
if (s < 0) {
std::cout << "Invalid speed value!\n";
return;
}
speed_ = s;
}
int speed() const noexcept {
return speed_;
}
};
This class is intentionally small, but the pattern scales: keep representation private, expose a stable interface, and enforce invariants where state changes happen.
A common beginner pattern is to create “getter/setter” pairs for everything. In modern OOP design, a more useful approach is to expose operations that match the domain. This typically results in fewer public functions, better invariants, and clearer intent.
#include <iostream>
#include <string>
class BankAccount {
private:
std::string holder_;
double balance_{0.0}; // invariant: balance_ >= 0.0
public:
explicit BankAccount(std::string name, double initial = 0.0)
: holder_(std::move(name)), balance_(initial < 0.0 ? 0.0 : initial) {}
const std::string& holder() const noexcept { return holder_; }
double balance() const noexcept { return balance_; }
void deposit(double amount) {
if (amount <= 0.0) {
std::cout << "Invalid deposit amount.\n";
return;
}
balance_ += amount;
}
bool withdraw(double amount) {
if (amount <= 0.0 || amount > balance_) {
std::cout << "Withdrawal failed.\n";
return false;
}
balance_ -= amount;
return true;
}
};
Two C++23-friendly design points to notice:
std::string by value, move it in, and expose a const& view.
In C++, class and struct are the same feature with different defaults:
struct: members default to publicclass: members default to privateThe real difference is intent and convention:
struct for simple aggregates and “plain data” where public members are appropriate
(often paired with non-member helper functions).
class when you have invariants to protect, behavior to expose, and internal details to hide.
const correctness to communicate guarantees and enable compiler optimization.The next lessons build from this foundation: you’ll create classes that behave like real-world concepts, design clean interfaces, and learn how access control supports both safety and long-term maintainability. By the time you reach the quiz, you should be able to look at a class and quickly answer: “What does this type promise?” and “What does it protect?”