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.