A technical primer covering memory management, object-oriented design, the STL, and Java's JVM model — from pointers to polymorphism.
30 slides · 2 parts
Goals & overview
Stack, heap, smart pointers
Classes, inheritance, vtable
Containers, algorithms
Lambdas, move, exceptions
JVM, interfaces, generics
Streams, lambdas, exceptions
Comparison & takeaways
Memory · OOP · STL · Modern Features
new, free with deleteA pointer stores a memory address. Declare with *, dereference with * to get the value, use & to get an address.
Adding 1 to a pointer advances it by sizeof(T) bytes. Useful for traversing arrays but dangerous if out-of-bounds.
A dangling pointer points to freed memory — undefined behaviour. Always set to nullptr after deleting.
Double-free, memory leak, buffer overflow. Raw pointers require strict discipline — prefer smart pointers instead.
Only one unique_ptr can own a resource at a time. Cannot be copied — only moved.
The destructor calls delete automatically when the pointer goes out of scope. Zero overhead vs raw pointer.
Preferred factory function. Exception-safe and avoids redundant type names. Available from C++14.
Use std::move() to transfer ownership. The original pointer becomes nullptr after the move.
Multiple shared_ptrs share one resource. An internal counter tracks how many owners exist. Freed when count hits 0.
If A owns B and B owns A via shared_ptr, the count never reaches 0 — memory leaks permanently.
Holds a reference without incrementing the count. Must be locked to access — safely detects expired pointers.
Use shared_ptr for the primary owner; weak_ptr for observers or back-references to break cycles.
private — class only | protected — class + derived | public — everyone
Constructor initialises the object; destructor cleans up. Use initialiser lists for efficiency: Dog(string n) : name(n) {}
If you define a destructor, copy constructor, or copy-assignment, you likely need all three. With move semantics: the Rule of Five.
Mark a method const to promise it won't modify the object. Allows calling on const references.
class Lab : public Dog — the derived class IS-A Dog. Public and protected members are inherited as-is.
C++ allows a class to inherit from multiple bases: class Amphibian : public Land, public Water. Risk: the Diamond Problem.
Use virtual on the shared base to avoid diamond duplication. Ensures only one copy of the base is inherited.
In the derived initialiser list: Lab(string n) : Dog(n, 1) {} — explicitly chains to the base constructor.
Declares a method as overridable. The compiler builds a vtable so the right version is called at runtime.
Compiler-verified re-implementation. Catches signature mismatches at compile time — prevents silent bugs.
= 0 makes a method abstract. The class cannot be instantiated; derived classes must provide an implementation.
A hidden lookup table built per class. A virtual call dereferences the vtable at runtime — tiny overhead, great flexibility.
A class with at least one pure virtual method. Defines a contract — derived classes must implement all pure virtuals before they can be instantiated.
A fully abstract class (all methods pure virtual, no data) mimics Java/C# interfaces. Commonly used for DI and plugin patterns.
Always declare base class destructors virtual. Otherwise deleting a derived object through a base pointer causes undefined behaviour (memory leak).
Called when initialising from another object. Default does shallow copy — override for deep copy when owning heap resources.
Dog(const Dog& other);
Called when assigning from another object. Must handle self-assignment and free old resources before copying.
Dog& operator=(const Dog& o);
Transfers resources from a temporary (rvalue). Avoids deep copy — just steal the pointer and null the source.
Dog(Dog&& other) noexcept;
Same idea as move constructor but for assignment. Free existing resources, steal from source, null the source.
Dog& operator=(Dog&& o) noexcept;
| Container | Access | Insert / Erase | Ordered | Best Use Case |
|---|---|---|---|---|
| vector<T> | O(1) | O(1) amort. end / O(n) middle | N/A | Default sequence; cache-friendly |
| deque<T> | O(1) | O(1) both ends | N/A | Queue-like access at front & back |
| list<T> | O(n) | O(1) anywhere | N/A | Frequent mid-sequence insert/erase |
| map<K,V> | O(log n) | O(log n) | Yes (BST) | Sorted key–value; range iteration |
| unordered_map<K,V> | O(1) avg | O(1) avg | No (hash) | Fastest key–value lookups |
| set<T> | O(log n) | O(log n) | Yes | Unique sorted elements |
| priority_queue<T> | O(1) top | O(log n) | Heap | Always-max (or min) at top |
find, find_if, count, binary_search, lower_bound, upper_bound
sort, stable_sort, partial_sort, nth_element — all accept custom comparators
transform, for_each, copy, copy_if, fill, replace, remove_if
accumulate, reduce, inner_product, min_element, max_element
Write one algorithm that works for any type. The compiler generates specialised versions at compile time — zero runtime overhead.
Parameterise an entire class by type(s). Foundation of every STL container. Type checking happens at compile time.
Provide a custom implementation for a specific type when the generic version isn't optimal: template<> class Stack<bool>
Accept an arbitrary number of type parameters via typename... Args. Foundation of std::tuple and perfect forwarding.
[capture](params) { body } — [=] capture all by value, [&] by reference, or name specific variables.
Assign to auto for zero-overhead; use std::function<R(Args)> when you need type-erasure or runtime polymorphism.
Pass inline to sort, find_if, transform etc. Cleaner than writing separate functor classes for every comparator.
An lvalue has a name / address (persists). An rvalue is temporary — a literal, a returned value. && binds to rvalues.
Instead of copying a 1 GB buffer, a move constructor steals the pointer in O(1). Huge performance win for containers and strings.
Casts an lvalue to rvalue — tells the compiler "I no longer need the original." The moved-from object is in a valid but unspecified state.
std::forward<T> preserves value category through template functions — critical for generic factory patterns.
Throw any type (prefer standard exceptions). Catch by const& to avoid slicing. Catch-all: catch (...)
std::runtime_error, std::logic_error, std::out_of_range, std::bad_alloc — all derive from std::exception.
Promise a function won't throw. Enables compiler optimisations and is required for move operations to be used in containers.
C++ has no finally. Use RAII — destructors run even if an exception unwinds the stack. Smart pointers and lock_guard exploit this.
JVM · OOP · Collections · Streams
Bytecode is OS-agnostic. Any machine with a JVM can run the same .class file — Windows, Linux, macOS, Android.
The GC automatically reclaims heap memory. Common algorithms: G1GC (default), ZGC, Shenandoah. No manual delete needed.
The JVM profiles at runtime and compiles frequently-executed (hot) methods to native code. Long-running Java can approach C++ speed.
Heap (objects), Stack (frames), Metaspace (class metadata), Code Cache (JIT output). Each has separate GC policies.
byte, short, int, long
float, double
boolean, char
Integer, Long, Double, Boolean… Autoboxing converts automatically between primitive and wrapper.
Immutable reference type. String pool interns literals. Use StringBuilder for concatenation in loops.
Local type inference — compiler infers the type from the initialiser. Only works for local variables.
All classes inherit from Object. Methods like toString(), equals(), hashCode() are inherited and often overridden.
GC handles deallocation. Use try-with-resources and AutoCloseable for deterministic resource cleanup (files, sockets).
public · protected · package-private (default) · private. Default is stricter than C++.
static members belong to the class, not an instance. static methods can't access this.
Java allows only one parent class (extends). This avoids the diamond problem but restricts reuse — compensated by interfaces.
Call the parent constructor with super(args) as the first line. Access parent methods with super.methodName().
Compile-time verification that you're actually overriding an inherited method. Prevents typo bugs that silently create a new method instead.
final on a class prevents subclassing. final on a method prevents overriding. String is a final class.
Pure contract — all methods are public abstract by default. A class can implement multiple interfaces. No instance variables (only constants).
Interfaces can have concrete default methods — allows adding behaviour to existing interfaces without breaking all implementors.
Can have fields, constructors, concrete methods, and abstract methods. Used when you want partial implementation shared across subclasses.
Interface for capability contracts (Runnable, Comparable). Abstract class for shared state and template pattern inheritance.
| Interface | Impl Class | Ordered | Duplicates | Thread-Safe Alt |
|---|---|---|---|---|
| List | ArrayList | Yes (insertion) | Yes | CopyOnWriteArrayList |
| List | LinkedList | Yes (insertion) | Yes | — |
| Set | HashSet | No | No | ConcurrentSkipListSet |
| Set | TreeSet | Sorted | No | — |
| Map | HashMap | No | Keys: No | ConcurrentHashMap |
| Map | TreeMap | Sorted by key | Keys: No | — |
| Queue | PriorityQueue | Heap order | Yes | PriorityBlockingQueue |
class Box<T> — T is a type placeholder. Replaced at compile time; enforces type safety without casts.
<T extends Comparable<T>> — restricts T to types that implement Comparable. Enables calling .compareTo() inside the generic code.
Generic type info is erased at runtime — the JVM only sees raw types. This differs from C++ templates which generate type-specific code.
List<?> — unknown type. List<? extends Number> — covariant (read-only). List<? super Integer> — contravariant (write).
Lazy — return a new Stream. Examples: filter, map, flatMap, distinct, sorted, limit, skip
Eager — trigger computation. Examples: collect, count, reduce, forEach, findFirst, anyMatch
Checked (extends Exception) — must be declared or caught. Unchecked (extends RuntimeException) — optional. Error = JVM-level, unrecoverable.
Resources implementing AutoCloseable are closed automatically. No finally block needed for cleanup — cleaner and exception-safe.
Catch multiple exception types in one block: catch (IOException | SQLException e). Reduces boilerplate.
Extend Exception or RuntimeException. Add context via constructor. Prefer unchecked for unrecoverable programming errors.
C++ gives direct memory control — smart pointers make it safe via RAII
OOP pillars (encapsulation, inheritance, polymorphism, abstraction) apply to both languages with different syntax
STL containers + algorithms + templates unlock powerful generic programming in C++
Modern C++ (lambdas, move semantics) eliminates most manual memory work
Java's JVM model prioritises portability, automatic memory, and runtime adaptability
Java interfaces + generics + streams enable clean, expressive backend architecture