20 topics
← Back to Quick Reference/
Topic 15
CCllaasssseess ((OOOOPP BBaassiiccss))
Encapsulation · Constructors · RAII · Inheritance · Virtual · Access Control
C++17 · Advanced ReferenceOOP in C++
01Classes 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.
Encapsulation— private data, public interface. Invariants are maintained inside the class. - 2.
Inheritance— a derived class gets all members of a base class and can add or override behaviour. - 3.
Polymorphism— virtual functions let a base pointer/reference call the derived class's override at runtime. - 4.
Abstraction— pure virtual functions define an interface without an implementation (abstract class).
Design rules
- 1.Always give base classes a
virtualdestructor — deleting a derived object through a base pointer is UB without it. - 2.Mark overrides with
override— the compiler catches signature mismatches that would silently create a new function. - 3.Use
expliciton single-argument constructors — prevents surprising implicit conversions. - 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
02class 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 list | Members are initialized before the constructor body runs. More efficient than assigning in the body. Required for const and reference members. |
| const method | Declared 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
03class 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 ctor | C++11: one constructor calls another. Avoids duplicating initialization logic. |
| explicit | Prevents Widget w = 5 (implicit). Allows Widget w{5} (direct). Use on any single-arg constructor. |
| noexcept | Move 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
| RAII | Resource Acquisition Is Initialization. Constructor acquires, destructor releases. Works correctly even when exceptions are thrown. |
| = delete copy | Non-copyable resources (file handles, mutexes, sockets) should delete copy constructor and assignment. |
| move ctor | Transfer ownership by moving the resource pointer, then null out the source so the destructor is a no-op. |
| examples | std::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
05class 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 dtor | Required 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. |
| override | Tells the compiler this must override a virtual function. Compile error if the signature doesn't match. |
| protected | Accessible 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) };
| vtable | A table of function pointers, one per class with virtual functions. Each object stores a hidden vptr to its class's vtable. |
| overhead | One pointer per object (vptr, 8 bytes). One indirect function call per virtual dispatch. Negligible in most code. |
| final | Prevents a class from being inherited from, or a virtual function from being overridden. Enables devirtualization optimizations. |
| non-virtual | Resolved at compile time based on the static type of the pointer/reference — always calls the declared class's version. |
Access Control · friend · static
07class 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)
| public | Accessible everywhere. Part of the class's public interface. |
| protected | Accessible inside the class and derived classes. Not accessible externally. |
| private | Accessible only inside the class (and friends). Default for class members. |
| friend | Grants full access to a specific class or function. Use sparingly — it breaks encapsulation. |
| static | Shared across all instances. No this pointer. Define (and initialize) outside the class in a .cpp file (or inline in C++17). |