20 topics
/
← Back to Quick Reference
Topic 15

Classes (OOP Basics)

Encapsulation · Constructors · RAII · Inheritance · Virtual · Access Control

C++17 · Advanced Reference

OOP in C++

01

Classes and encapsulation

A class bundles data (members) and behaviour (methods) under controlled access. The key principle is encapsulation — hide the implementation details, expose only what callers need. This lets you change the internals without breaking any code that uses the class.

The four pillars

  1. 1.Encapsulation — private data, public interface. Invariants are maintained inside the class.
  2. 2.Inheritance — a derived class gets all members of a base class and can add or override behaviour.
  3. 3.Polymorphism — virtual functions let a base pointer/reference call the derived class's override at runtime.
  4. 4.Abstraction — pure virtual functions define an interface without an implementation (abstract class).

Design rules

  1. 1.Always give base classes a virtual destructor — deleting a derived object through a base pointer is UB without it.
  2. 2.Mark overrides with override — the compiler catches signature mismatches that would silently create a new function.
  3. 3.Use explicit on single-argument constructors — prevents surprising implicit conversions.
  4. 4.Follow the Rule of Zero or Rule of Five — never define some special members but not others.

Prefer composition over inheritance for code reuse. Inheritance is for "is-a" relationships; composition is for "has-a".

Class Anatomy

02
class BankAccount {
public:                          // accessible by everyone
  BankAccount(std::string owner, double initial)
    : owner_(owner), balance_(initial) {}   // member initializer list

  void deposit(double amount) {
    if (amount > 0) balance_ += amount;
  }

  bool withdraw(double amount) {
    if (amount > balance_) return false;
    balance_ -= amount;
    return true;
  }

  double balance() const { return balance_; }  // const: won't modify
  const std::string& owner() const { return owner_; }

private:                         // accessible only inside the class
  std::string owner_;
  double      balance_;          // trailing _ convention for members
};

BankAccount acc("Alice", 100.0);
acc.deposit(50.0);
acc.balance();   // 150.0
// acc.balance_ = 0;  ❌ private — compile error
initializer listMembers are initialized before the constructor body runs. More efficient than assigning in the body. Required for const and reference members.
const methodDeclared with const after the parameter list. this is const T* — cannot modify members.
trailing _Common convention to name private members (balance_). Distinguishes from local variables and parameters.

Constructors & Special Members

03
class Widget {
public:
  // Default constructor
  Widget() : value_(0), name_("default") {}

  // Parameterized constructor
  Widget(int v, std::string n) : value_(v), name_(std::move(n)) {}

  // Delegating constructor (C++11) — avoids repeating init logic
  Widget(int v) : Widget(v, "unnamed") {}

  // Copy constructor (deep copy if needed)
  Widget(const Widget& other) = default;   // compiler default is fine here

  // Move constructor (C++11)
  Widget(Widget&& other) noexcept = default;

  // Copy assignment
  Widget& operator=(const Widget& other) = default;

  // Move assignment
  Widget& operator=(Widget&& other) noexcept = default;

  // Destructor
  ~Widget() = default;

  // Explicit: prevents implicit conversion from int
  explicit Widget(int v) : value_(v) {}
  // Widget w = 5;   ❌ implicit — blocked by explicit
  // Widget w{5};    ✅ direct init — allowed

private:
  int value_;
  std::string name_;
};
delegating ctorC++11: one constructor calls another. Avoids duplicating initialization logic.
explicitPrevents Widget w = 5 (implicit). Allows Widget w{5} (direct). Use on any single-arg constructor.
noexceptMove operations should be noexcept — containers like vector use move only if noexcept, otherwise copy.
= default / delete= default: compiler generates. = delete: disabled entirely. Both appear in the declaration.
Mark move operations noexcept. Without it, std::vector may copy instead of move during reallocation — silently negating the performance benefit of move semantics.

RAII — Resource Management

04
// RAII: Resource Acquisition Is Initialization
// Acquire the resource in the constructor, release in the destructor
// Guarantees cleanup even when exceptions are thrown

class FileHandle {
public:
  explicit FileHandle(const std::string& path)
    : file_(std::fopen(path.c_str(), "r")) {
    if (!file_) throw std::runtime_error("cannot open: " + path);
  }

  ~FileHandle() {
    if (file_) std::fclose(file_);   // always runs — even on exception
  }

  // Non-copyable (resource shouldn't be duplicated)
  FileHandle(const FileHandle&) = delete;
  FileHandle& operator=(const FileHandle&) = delete;

  // Movable (transfer ownership)
  FileHandle(FileHandle&& o) noexcept : file_(o.file_) { o.file_ = nullptr; }

  FILE* get() const { return file_; }

private:
  FILE* file_;
};

{
  FileHandle f("data.txt");  // opens
  readLines(f.get());
}   // ← destructor called here — file closed automatically
RAIIResource Acquisition Is Initialization. Constructor acquires, destructor releases. Works correctly even when exceptions are thrown.
= delete copyNon-copyable resources (file handles, mutexes, sockets) should delete copy constructor and assignment.
move ctorTransfer ownership by moving the resource pointer, then null out the source so the destructor is a no-op.
examplesstd::unique_ptr, std::lock_guard, std::ifstream, std::vector — all use RAII internally.
RAII is the most important C++ idiom. It eliminates resource leaks without try/finally, even across multiple return paths and exceptions.

Inheritance & Virtual Dispatch

05
class Shape {
public:
  Shape(std::string color) : color_(color) {}
  virtual ~Shape() = default;   // ✅ virtual destructor — always for base classes

  virtual double area() const = 0;        // pure virtual — must override
  virtual std::string describe() const {  // virtual — may override
    return color_ + " shape, area=" + std::to_string(area());
  }

protected:
  std::string color_;   // accessible in derived classes, not outside
};

class Circle : public Shape {
public:
  Circle(std::string color, double r) : Shape(color), radius_(r) {}

  double area() const override {         // override — compile error if sig wrong
    return 3.14159 * radius_ * radius_;
  }

private:
  double radius_;
};

Shape* s = new Circle("red", 5.0);
s->area();        // calls Circle::area() — runtime dispatch
s->describe();    // calls Shape::describe() — calls Circle::area() through vtable
delete s;         // calls Circle::~Circle() then Shape::~Shape() — correct
virtual dtorRequired on any base class. Without it, deleting a derived object through a base pointer only calls the base destructor — resource leak.
= 0 (pure virtual)Makes the class abstract — cannot instantiate directly. Derived classes must provide an implementation.
overrideTells the compiler this must override a virtual function. Compile error if the signature doesn't match.
protectedAccessible in the class itself and all derived classes, but not by external code.

Virtual Dispatch & final

06
// ── Virtual dispatch (runtime polymorphism) ──────────────────
// Each class with virtual functions gets a vtable (function pointer table)
// Each object stores a hidden vptr pointing to its class's vtable

class Base {
public:
  virtual void foo() { std::cout << "Base\n"; }
  void bar()         { std::cout << "Base::bar\n"; }  // non-virtual
};

class Derived : public Base {
public:
  void foo() override { std::cout << "Derived\n"; }
  void bar()          { std::cout << "Derived::bar\n"; }
};

Base* p = new Derived();
p->foo();   // "Derived" — virtual: runtime dispatch via vtable
p->bar();   // "Base::bar" — non-virtual: compile-time dispatch on Base*

// ── final — prevent further overriding or inheritance ─────────
class Leaf final : public Base {       // cannot inherit from Leaf
  void foo() override final { }        // cannot override foo in Leaf's children
};

// ── override — catch signature mistakes at compile time ───────
class Bad : public Base {
  void fooo() override { }   // ❌ typo — compile error (no Base::fooo)
};
vtableA table of function pointers, one per class with virtual functions. Each object stores a hidden vptr to its class's vtable.
overheadOne pointer per object (vptr, 8 bytes). One indirect function call per virtual dispatch. Negligible in most code.
finalPrevents a class from being inherited from, or a virtual function from being overridden. Enables devirtualization optimizations.
non-virtualResolved at compile time based on the static type of the pointer/reference — always calls the declared class's version.

Access Control · friend · static

07
class A {
public:    // accessible everywhere
  int pub = 1;
protected: // accessible in A and derived classes
  int prot = 2;
private:   // accessible only inside A
  int priv = 3;

  // friend — grants full access to a specific class or function
  friend class B;
  friend void inspect(const A&);
};

class B {
  void test(A& a) { a.priv; }   // ✅ friend has access
};

// ── Inheritance access levels ─────────────────────────────────
class PubDerived  : public    A { };  // pub→pub, prot→prot
class ProtDerived : protected A { };  // pub→prot, prot→prot
class PrivDerived : private   A { };  // pub→priv, prot→priv

// ── Static members — shared across all instances ──────────────
class Counter {
public:
  static int count;           // declaration
  Counter() { count++; }
  ~Counter() { count--; }
};
int Counter::count = 0;       // definition (C++17: inline static int count = 0 in class)
publicAccessible everywhere. Part of the class's public interface.
protectedAccessible inside the class and derived classes. Not accessible externally.
privateAccessible only inside the class (and friends). Default for class members.
friendGrants full access to a specific class or function. Use sparingly — it breaks encapsulation.
staticShared across all instances. No this pointer. Define (and initialize) outside the class in a .cpp file (or inline in C++17).