C++ PROGRAMMING ยท 15 MIN STUDY

Composition, encapsulation, and operator overloading

Design class interfaces with composition and encapsulation, and overload operators only when natural.

Learning objectives

  • Explain encapsulation and class invariants.
  • Use composition to build a type from other types.
  • Understand when operator overloading improves an interface.
  • Use const member functions for read-only operations.

Prerequisites: classes, constructors, access control, and const member functions. Review Classes and objects, Constructors and destructors, and Const correctness first.

Encapsulation

Encapsulation groups data and operations and controls how callers access the internal state. A class can keep data private and expose public operations that preserve its rules. This helps protect the object's invariant.

Composition: has-a relationship

Composition means a class contains objects of other types as members. For example, a Car can contain an Engine. This is different from inheritance, which represents an is-a relationship.

class Engine {
public:
    void start();
};

class Car {
private:
    Engine engine;
public:
    void start() { engine.start(); }
};

Const member functions

A member function declared with const after its parameter list promises not to modify the object's non-mutable data members through this. Use const member functions for operations that inspect state, such as size() or area().

Operator overloading

C++ allows a class to define behavior for operators such as +, ==, or <<. Overload an operator only when its meaning is natural and unsurprising for the type. For example, adding two mathematical vectors with + can be clear. Overloading + to send an email would be confusing.

struct Point {
    int x{};
    int y{};
};

Point operator+(const Point& left, const Point& right) {
    return {left.x + right.x, left.y + right.y};
}

This example makes Point + Point mean component-wise addition. The operator returns a new Point rather than unexpectedly modifying either operand.

Common mistakes

  • Making all data public and then relying on every caller to preserve invariants.
  • Using inheritance where composition better describes the relationship.
  • Overloading an operator with a surprising meaning.
  • Forgetting const on a member function that only reads the object.

Practice

  1. Design a BankAccount class with private balance and public deposit/withdraw functions.
  2. Model a Computer that contains a CPU using composition.
  3. Overload equality for a simple Point type.
  4. Explain why a read-only getter should often be a const member function.

Practice solutions

Show worked solutions
  1. Keep balance private. Let the constructor establish a non-negative balance, let deposit reject negative amounts, and let withdraw reject negative or unaffordable withdrawals. Every public operation must preserve the invariant that the balance is non-negative.
  2. #include <string>
    struct CPU { std::string model; };
    struct Computer {
        CPU processor; // a Computer has a CPU
    };
    The CPU is a member object whose lifetime is tied to the containing Computer.
  3. struct Point {
        int x{};
        int y{};
        bool operator==(const Point& other) const {
            return x == other.x && y == other.y;
        }
    };
    The const member compares both coordinates without changing either point.
  4. A const getter promises not to modify the observable state of the object. It can therefore be called on const objects and communicates read-only intent.

Check your understanding

Question: What relationship does composition represent?

Answer: A has-a relationship: one object contains or uses another object as part of its design.

Summary

Encapsulation protects invariants, composition builds types from collaborating objects, const member functions communicate read-only behavior, and operator overloads should preserve natural expectations.

References