C++ OOP Cheat Sheet — Classes, Templates, RAII, Smart Pointers | Dataplexa

C++ OOP

classes  ·  templates  ·  RAII  ·  smart ptrs  ·  inheritance  ·  polymorphism  ·  move semantics

Sheet 3 of 3 C++17 Intermediate Printable

Class Anatomy

constructor · destructor · members · access
Class Definition
class Animal {
private:
    string name;
    int    age;

public:
    // Constructor
    Animal(string n, int a)
        : name(n), age(a) {}  // init list

    // Destructor
    ~Animal() {
        cout << "Destroyed\n";
    }

    // Getter (const — won't modify)
    string getName() const {
        return name;
    }

    // Method
    virtual void speak() const {
        cout << "...";
    }
};
Constructors — All Types
class Box {
public:
    int w, h;

    // Default constructor
    Box() : w(0), h(0) {}

    // Parameterised
    Box(int w, int h)
        : w(w), h(h) {}

    // Copy constructor
    Box(const Box& other)
        : w(other.w), h(other.h) {}

    // Move constructor (C++11)
    Box(Box&& other) noexcept
        : w(other.w), h(other.h) {
        other.w = other.h = 0;
    }
};

// Create objects
Box b1;               // default
Box b2(10, 5);        // param
Box b3 = b2;          // copy
Box b4 = move(b2);   // move
Access Specifiers & static
class Counter {
private:              // class only
    int count;

protected:            // class + subclasses
    int step;

public:               // everyone
    static int total; // shared across all

    Counter() : count(0), step(1)
        { total++; }

    void increment() { count += step; }
    int  get() const  { return count; }

    // Static method
    static int getTotal()
        { return total; }
};
// Define static outside class:
int Counter::total = 0;
Always use initialiser lists: member(val) — instead of assignment in the constructor body. They're faster (direct-init) and required for const and reference members.

Inheritance & Polymorphism

virtual · override · abstract · dynamic_cast
Single Inheritance
class Animal {
protected:
    string name;
public:
    Animal(string n) : name(n) {}
    virtual void speak() const {
        cout << "...\n";
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    Dog(string n)
        : Animal(n) {}        // call base ctor

    void speak() const override {
        cout << name << ": Woof!\n";
    }
};

class Cat : public Animal {
public:
    Cat(string n) : Animal(n) {}
    void speak() const override {
        cout << name << ": Meow!\n";
    }
};
Polymorphism via Base Pointer
// Runtime polymorphism
vector<Animal*> zoo;
zoo.push_back(new Dog("Rex"));
zoo.push_back(new Cat("Luna"));

for (auto* a : zoo)
    a->speak();     // calls correct version!
// Rex: Woof!
// Luna: Meow!

// dynamic_cast — safe downcast
Dog* dp = dynamic_cast<Dog*>(zoo[0]);
if (dp)  dp->speak();  // safe

// Clean up
for (auto* a : zoo) delete a;
Abstract Classes & Interfaces
// Pure virtual = abstract class
class Shape {
public:
    // Pure virtual — must override
    virtual double area() const = 0;
    virtual double perimeter() const = 0;
    virtual ~Shape() = default;

    // Non-virtual common method
    void print() const {
        cout << "Area=" << area();
    }
};

class Circle : public Shape {
    double r;
public:
    Circle(double r) : r(r) {}
    double area() const override
        { return 3.14159 * r * r; }
    double perimeter() const override
        { return 2 * 3.14159 * r; }
};
// Shape s;  ← compile error!
Circle c(5.0);  // OK
Always make base destructors virtual! Without virtual ~Base(), deleting a derived object through a base pointer causes undefined behaviour — the derived destructor never runs, leaking resources.

Templates

function template · class template · specialisation
Function Templates
// Works for any type T
template <typename T>
T maxOf(T a, T b) {
    return (a > b) ? a : b;
}

maxOf(3, 7)       // int: 7
maxOf(3.14, 2.7)  // double: 3.14
maxOf<int>(3, 7)  // explicit type

// Multiple type params
template <typename T, typename U>
auto add(T a, U b) {
    return a + b;
}
add(1, 2.5)  // double: 3.5
Class Templates
template <typename T>
class Stack {
    vector<T> data;
public:
    void push(const T& v)
        { data.push_back(v); }
    void pop()
        { data.pop_back(); }
    T& top()
        { return data.back(); }
    bool empty() const
        { return data.empty(); }
};

Stack<int>    si;
Stack<string> ss;
si.push(42);
ss.push("hello");

Operator Overloading

+ · == · << · [] · ++
Common Operator Overloads
class Vec2 {
public:
    double x, y;
    Vec2(double x, double y)
        : x(x), y(y) {}

    // Arithmetic
    Vec2 operator+(const Vec2& o) const
        { return {x+o.x, y+o.y}; }

    // Equality
    bool operator==(const Vec2& o) const
        { return x==o.x && y==o.y; }

    // Stream output (friend)
    friend ostream& operator<<(
        ostream& os, const Vec2& v) {
        return os << "(" << v.x
                   << "," << v.y << ")";
    }
};

Vec2 a{1,2}, b{3,4};
cout << a + b;  // (4,6)

RAII — Resource Acquisition Is Initialisation

unique_ptr · shared_ptr · weak_ptr · make_unique
The RAII Pattern
// RAII: tie resource lifetime
// to object scope — no raw new/delete

class FileHandle {
    FILE* fp;
public:
    FileHandle(const char* path)
        : fp(fopen(path, "r")) {}

    ~FileHandle() {
        if (fp) fclose(fp);  // auto-close
    }
    FILE* get() { return fp; }
};

{
    FileHandle f("data.txt");
    // use f.get() ...
}  // file closed here — guaranteed!
Smart PtrOwnershipUse when
unique_ptrSingle owner Default — exclusive ownership
shared_ptrShared (ref count) Multiple owners needed
weak_ptr Non-owning observerBreak shared_ptr cycles
unique_ptr — exclusive ownership
#include <memory>

// Create — prefer make_unique
auto p = make_unique<Dog>("Rex");

// Use like a pointer
p->speak();
(*p).speak();

// Can't copy — only move
auto p2 = move(p);   // p is now null

// Array version
auto arr = make_unique<int[]>(10);
arr[0] = 42;

// Get raw pointer (non-owning)
Dog* raw = p2.get();

// Release and reset
p2.reset();    // deletes, sets null
// No need to call delete!
shared_ptr & weak_ptr
// shared_ptr — reference counted
auto sp1 = make_shared<Cat>("Luna");
auto sp2 = sp1;  // both own it

sp1.use_count();   // 2
sp1.reset();       // count → 1
sp2.reset();       // count → 0, deleted

// weak_ptr — non-owning observer
auto sp = make_shared<int>(42);
weak_ptr<int> wp = sp;

// Must lock to use
if (auto locked = wp.lock())
    cout << *locked;  // safe
else
    cout << "expired";
Rule of Zero: If your class uses only smart pointers and standard library members, you don't need to write any destructor, copy/move constructor, or assignment operator. The compiler generates correct ones for free.

Move Semantics

rvalue · std::move · move ctor · noexcept
lvalue vs rvalue & std::move
// lvalue  — has a name / address
int x = 5;       // x is lvalue
string s = "hi"; // s is lvalue

// rvalue — temporary, no name
5;               // rvalue
string("hi");   // rvalue

// std::move — cast to rvalue
// signals "I'm done with this"
vector<int> a = {1,2,3};
vector<int> b = move(a);
// b has data, a is empty — O(1)!
// No copy made — just pointer swap
Rule of Five
// If you define ANY of these 5,
// define ALL 5 (or = default / = delete)

class Buf {
    int* data;
    size_t sz;
public:
    // 1. Destructor
    ~Buf() { delete[] data; }
    // 2. Copy constructor
    Buf(const Buf& o);
    // 3. Copy assignment
    Buf& operator=(const Buf& o);
    // 4. Move constructor
    Buf(Buf&& o) noexcept;
    // 5. Move assignment
    Buf& operator=(Buf&& o) noexcept;
};

Exception Handling

try · catch · throw · noexcept
try / catch / throw
#include <stdexcept>

double divide(double a, double b) {
    if (b == 0)
        throw invalid_argument("div/0");
    return a / b;
}

try {
    cout << divide(10, 0);
}
catch (const invalid_argument& e) {
    cerr << "Error: " << e.what();
}
catch (const exception& e) {
    cerr << "Std err: " << e.what();
}
catch (...) {
    cerr << "Unknown error";
}
Standard Exception Hierarchy
// <stdexcept> exceptions
runtime_error      // general runtime
logic_error        // programming error
invalid_argument   // bad argument
out_of_range       // index out of bounds
overflow_error     // arithmetic overflow

// Custom exception
class MyError : public runtime_error {
public:
    MyError(const string& msg)
        : runtime_error(msg) {}
};
throw MyError("something broke");

Namespaces, const & Modern C++ Essentials

namespace · constexpr · nullptr · using · decltype
Namespaces
// Define a namespace
namespace math {
    double sqrt(double x);
    const double PI = 3.14159;
}

// Use with scope resolution
math::sqrt(9.0);
math::PI;

// using declaration
using math::PI;
PI;  // OK now

// Nested namespace (C++17)
namespace app::net::http {
    void connect();
}
// Avoid: using namespace std;
// in headers — pollutes global scope
constexpr & const
// const — runtime constant
const int MAX = 100;

// constexpr — compile-time constant
constexpr int SIZE = 1024;
constexpr double PI = 3.14159;

// constexpr function
constexpr int square(int x)
    { return x * x; }

constexpr int s = square(5);  // 25 at compile time
int arr[square(4)];            // arr[16]

// nullptr — type-safe null
int* p = nullptr;   // not NULL or 0
if (!p) cout << "null";
Type Inference & Utilities
// auto — infer type
auto x = 42;           // int
auto it = v.begin();   // iterator

// decltype — get type of expr
decltype(x) y = 10;   // int y

// Type aliases
using IntVec = vector<int>;
using Fn = function<int(int)>;

// static_assert — compile-time check
static_assert(sizeof(int) == 4,
              "Need 32-bit int");

// if constexpr (C++17)
template<typename T>
void print(T t) {
    if constexpr (is_integral_v<T>)
        cout << "int: " << t;
    else
        cout << "other: " << t;
}

C++ OOP Mastery Checklist

sheet 3 complete
Classes & OOPKey point
Use initialiser lists : mem(val) {}
Mark non-modifying methods fn() const
Virtual destructor in base virtual ~Base()
Override explicitly void fn() override
Abstract class = 0 pure virtual
Safe downcast dynamic_cast<T*>
Smart Ptrs & RAIIKey point
Exclusive ownership unique_ptr<T>
Shared ownership shared_ptr<T>
Non-owning reference weak_ptr<T>
Create smart ptr make_unique / make_shared
Transfer unique_ptr std::move(ptr)
Rule of Zero prefer smart ptrs
Templates & ModernKey point
Generic function template<typename T>
Generic class template<typename T> class
Compile-time constant constexpr
Type inference auto
Type alias using Alias = Type;
Null pointer nullptr
You've completed all 3 C/C++ sheets!  ·  Next explore: Go Basics · Go Concurrency · Rust Basics · Rust Error Handling — or revisit Java Collections for comparison.