C++ OOP Cheat Sheet
Covers C++ classes, constructors, inheritance, virtual functions and polymorphism, operator overloading, and the four core OOP pillars.
Class Basics
Defining a class with constructors, destructor, and access specifiers.
class Rectangle {private: double width, height;public: Rectangle(double w, double h) : width(w), height(h) {} // constructor, init list ~Rectangle() {} // destructor double area() const { return width * height; } // const member function void setWidth(double w) { width = w; }};Rectangle r(3.0, 4.0);std::cout << r.area(); // 12
Inheritance
Derive a class from a base class and reuse/extend its behavior.
class Shape {public: Shape(std::string name) : name(name) {} virtual double area() const = 0; // pure virtual -> Shape is abstract virtual ~Shape() = default; // virtual destructor for safe polymorphic deleteprotected: std::string name;};class Circle : public Shape {public: Circle(double r) : Shape("Circle"), radius(r) {} double area() const override { return 3.14159 * radius * radius; }private: double radius;};
Polymorphism
Call derived-class behavior through a base-class pointer or reference.
std::vector<std::unique_ptr<Shape>> shapes;shapes.push_back(std::make_unique<Circle>(2.0));for (const auto& s : shapes) { std::cout << s->area() << "\n"; // dynamic dispatch via virtual function}// Without 'virtual', this would statically bind to Shape::area() (if it existed)// override catches typos: compiler errors if the base has no matching virtual
Operator Overloading
Give custom types natural syntax for built-in operators.
class Vector2D {public: double x, y; Vector2D(double x, double y) : x(x), y(y) {} Vector2D operator+(const Vector2D& other) const { return Vector2D(x + other.x, y + other.y); } bool operator==(const Vector2D& other) const { return x == other.x && y == other.y; } friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) { return os << "(" << v.x << ", " << v.y << ")"; }};
The Four Pillars
Core OOP principles as expressed in C++.
- Encapsulation- Bundling data and methods together, restricting access via private/protected members.
- Abstraction- Exposing only essential behavior through an interface, e.g. a pure abstract base class.
- Inheritance- A derived class reuses and extends a base class's members via public/protected/private inheritance.
- Polymorphism- Same interface, different behavior; achieved at runtime via virtual functions or at compile time via templates/overloading.
- Composition- Building complex types by containing instances of other classes; often preferred over inheritance for flexibility ("favor composition over inheritance").
- Access Specifiers- public, protected, and private control the visibility of members to derived classes and outside code.
How Virtual Dispatch Works (vtable)
Understand the mechanism behind dynamic dispatch and its cost.
class Base {public: virtual void speak() const { std::cout << "Base\n"; } virtual ~Base() = default;};class Derived : public Base {public: void speak() const override { std::cout << "Derived\n"; }};// Each polymorphic object carries a hidden vptr to its class's vtable// (a static array of function pointers). Calling a virtual function is// one extra pointer indirection through the vptr - roughly constant// overhead, but it defeats inlining and can hurt cache locality in// tight loops over many small polymorphic objects.Base* b = new Derived();b->speak(); // vptr lookup -> Derived::speak, prints "Derived"// final prevents further overriding and lets the compiler devirtualizeclass Sealed final : public Derived { void speak() const final { std::cout << "Sealed\n"; }};
CRTP: Static Polymorphism
Achieve compile-time polymorphism without virtual function overhead.
// Curiously Recurring Template Pattern: base is templated on its own derived typetemplate <typename Derived>class Shape {public: double area() const { return static_cast<const Derived*>(this)->areaImpl(); // no vtable lookup }};class Square : public Shape<Square> {public: explicit Square(double s) : side(s) {} double areaImpl() const { return side * side; }private: double side;};template <typename T>double totalArea(const Shape<T>& s) { return s.area(); } // resolved at compile time// Trade-off: no runtime polymorphism (can't store mixed shapes in one// container without type erasure), but zero indirection and inlinable
Multiple & Virtual Inheritance
Combine multiple base classes and resolve the diamond problem.
class Animal { public: virtual void breathe() { std::cout << "breathing\n"; } };// Diamond problem: without 'virtual', Dog would get TWO Animal subobjectsclass Swimmer : public virtual Animal {};class Runner : public virtual Animal {};class Dog : public Swimmer, public Runner {}; // exactly one shared Animal baseDog d;d.breathe(); // unambiguous thanks to virtual inheritance// Mixin-style multiple inheritance for orthogonal capabilities (no diamond)class Printable { public: virtual void print() const = 0; virtual ~Printable() = default; };class Comparable { public: virtual bool equals(const void* o) const = 0; };class Token : public Printable, public Comparable { void print() const override { std::cout << "token\n"; } bool equals(const void* o) const override { return this == o; }};
Defaulted/Deleted Special Members & Rule of Five
Control exactly which special member functions the compiler generates.
class Resource {public: Resource() = default; Resource(const Resource&) = delete; // non-copyable Resource& operator=(const Resource&) = delete; Resource(Resource&&) noexcept = default; // movable Resource& operator=(Resource&&) noexcept = default; ~Resource() = default;};// Declaring ANY constructor suppresses the implicit default constructor;// declaring a destructor or copy ctor no longer auto-generates move members -// = default restores compiler-generated behavior explicitly and documents intentclass Base {public: Base() = default; Base(const Base&) = default; virtual ~Base() = default; // virtual, but still compiler-generated body Base& operator=(const Base&) = delete; // e.g. identity shouldn't be reassignable};
Advanced OOP Vocabulary
Terms that come up in interviews and real codebases beyond the four pillars.
- Object Slicing- Assigning a Derived object to a Base-by-value variable copies only the Base portion, silently dropping derived state and vtable.
- Covariant Return Types- An override may return a more-derived pointer/reference type than the base's virtual function, e.g. Derived* Clone() override where Base declares Base* Clone().
- Non-Virtual Interface (NVI) Idiom- Public non-virtual methods call private/protected virtual methods, letting the base class enforce pre/post-conditions around customizable behavior.
- Mixins- Small classes (often template-based) inherited purely to inject reusable behavior, not to model an is-a relationship.
- Type Erasure- Techniques (e.g. std::function, std::any, or a hand-rolled concept/model pair) that provide runtime polymorphism without a common base class.
- Diamond Problem- Ambiguity from multiple inheritance paths reaching a common base; resolved with virtual inheritance so only one base subobject exists.
- Empty Base Optimization (EBO)- The compiler can give an empty base class zero size within a derived object, useful for policy-based design without storage overhead.
Always give a base class a virtual (or protected non-virtual) destructor if it's meant to be used polymorphically - deleting a derived object through a base pointer without one is undefined behavior and skips the derived destructor.