OO Encapsulation  «Prev  Next»
Lesson 1

Describe the Concept of OOP and Encapsulation in C++

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:

  1. Why encapsulation is central to OOP and how it supports maintainability, safety, and change.
  2. What classes and objects are in C++ (and how they differ from “just structs with functions”).
  3. How member functions define an Abstract Data Type (ADT) by exposing behavior instead of fields.
  4. How access control works (public, private, protected) and why “private by default” matters.
  5. How a class differs from a struct (default access, intent, and conventional usage in modern codebases).

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).

What OOP means in modern C++

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.

Encapsulation vs information hiding

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.

Encapsulation in C++: invariants and contracts

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.”

Example 1: a small encapsulated type

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.

Example 2: behavior-first interface (preferred)

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:

Class vs struct in C++

In C++, class and struct are the same feature with different defaults:

The real difference is intent and convention:

Modern C++23 encapsulation habits

Where we go next

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?”


SEMrush Software