C++ Cheatsheet

Smart Pointers

Use this C++ reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Why Smart Pointers

Smart pointers are RAII wrappers around raw pointers. They call delete (or a custom deleter) automatically when they go out of scope, preventing memory leaks and double-frees.

#include <memory>

Three kinds:

TypeOwnershipShare?Overhead
unique_ptr<T>sole ownerNo (move only)Zero — same cost as raw pointer
shared_ptr<T>shared ownersYes (ref-counted)Reference count (atomic), control block
weak_ptr<T>non-owning observerobserves shared_ptrSmall (no ownership)

std::unique_ptr<T>

// Creation
std::unique_ptr<int> p1 = std::make_unique<int>(42);   // preferred (C++14)
std::unique_ptr<int> p2(new int(42));                  // avoid — exception-unsafe

// Array form
std::unique_ptr<int[]> arr = std::make_unique<int[]>(10);
arr[0] = 5;

// Access
*p1;              // dereference: 42
p1.get();         // raw pointer (don't store long-term)
p1->member;       // access member (for class types)
bool b = (bool)p1; // or p1 != nullptr

// Release and reset
int* raw = p1.release();   // relinquish ownership; p1 is now null; caller must delete
delete raw;

p1.reset();                // delete owned object; p1 becomes null
p1.reset(new int(99));     // delete old, own new object

// Transfer ownership (move only; cannot copy)
std::unique_ptr<int> p3 = std::move(p1);  // p1 is now null
// p2 = p1;  // compile error: copy deleted

// As function parameters / return values
std::unique_ptr<Foo> makeWidget() {
    return std::make_unique<Foo>(args);    // implicit move on return
}
void sink(std::unique_ptr<Foo> p);        // takes ownership
void borrow(const Foo& f);               // prefer: doesn't involve ownership
void borrow(Foo* f);                      // when null is meaningful

// Custom deleter
auto del = [](FILE* f){ std::fclose(f); };
std::unique_ptr<FILE, decltype(del)> file(std::fopen("a.txt", "r"), del);

std::shared_ptr<T>

// Creation
std::shared_ptr<int> sp1 = std::make_shared<int>(42);  // preferred (single allocation)
std::shared_ptr<int> sp2(new int(42));                  // two allocations

// Reference counting
std::shared_ptr<int> sp3 = sp1;    // sp1 and sp3 share ownership; count = 2
sp1.use_count();                    // current reference count (for debugging only)
sp1.unique();                       // true if use_count() == 1 (C++20: removed, use use_count()==1)

// Access — same operators as unique_ptr
*sp1;  sp1.get();  sp1->member;

// Reset
sp1.reset();              // release this shared_ptr's ownership; decrement count
sp1.reset(new int(99));   // release old, take new object

// Swap
sp1.swap(sp2);

// Aliasing constructor — share ownership but point to different object
std::shared_ptr<int> sp5(sp1, &some_int);  // same control block as sp1

// Custom deleter
std::shared_ptr<FILE> sf(std::fopen("a.txt","r"),
                          [](FILE* f){ std::fclose(f); });

// Array — shared_ptr<T[]> is C++17; make_shared for arrays is C++20
std::shared_ptr<int[]> sa = std::make_shared<int[]>(5);
sa[0] = 1;

enable_shared_from_this

Allows an object managed by shared_ptr to safely create a shared_ptr to itself:

class Node : public std::enable_shared_from_this<Node> {
public:
    std::shared_ptr<Node> getShared() {
        return shared_from_this();   // safe; do NOT return shared_ptr<Node>(this)
    }
};

auto n = std::make_shared<Node>();
auto n2 = n->getShared();   // same control block

std::weak_ptr<T>

Observes a shared_ptr without participating in ownership. Used to break reference cycles and for caches.

std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;    // does not increment ref count

wp.expired();                  // true if managed object has been destroyed
wp.use_count();                // number of shared_ptr owners (0 if expired)

// Must lock before use (returns empty shared_ptr if expired)
if (auto locked = wp.lock()) {
    *locked;   // safe to use
} else {
    // object already destroyed
}

wp.reset();                    // release the weak reference

Breaking Cycles

struct Parent;
struct Child {
    std::shared_ptr<Parent> parent;   // cycle if Parent has shared_ptr<Child>
};

struct Parent {
    std::weak_ptr<Child> child;       // weak reference breaks the cycle
};

Factory Helpers

// Always prefer make_* over new:
auto u = std::make_unique<T>(args...);    // C++14
auto s = std::make_shared<T>(args...);   // C++11

// make_shared: one heap allocation for object + control block (faster)
// shared_ptr<T>(new T): two allocations

// Allocate with custom allocator
std::allocate_shared<T>(alloc, args...);

Casting Smart Pointers

// For shared_ptr, use these instead of C++ casts:
std::static_pointer_cast<Derived>(base_sp);
std::dynamic_pointer_cast<Derived>(base_sp);  // returns empty if cast fails
std::const_pointer_cast<T>(const_sp);
std::reinterpret_pointer_cast<T>(sp);         // C++17

Ownership Patterns

// 1. Sole ownership — use unique_ptr
class Engine { };
class Car {
    std::unique_ptr<Engine> engine_ = std::make_unique<Engine>();
};

// 2. Shared ownership — use shared_ptr
class Texture { };
class Mesh {
    std::shared_ptr<Texture> texture_;
};

// 3. Observing — use raw pointer or weak_ptr (never own through raw pointer)
void render(const Texture* tex);       // doesn't own; tex must outlive function

// 4. Optional ownership — unique_ptr or optional<T>
std::unique_ptr<Widget> maybeWidget;   // null = no widget

Performance Notes

  • unique_ptr has zero overhead over a raw pointer in optimized builds.
  • shared_ptr has two overhead sources: an atomic reference count (increment/decrement) and an extra pointer to the control block.
  • Prefer unique_ptr and convert to shared_ptr only when sharing is actually needed.
  • Avoid cyclic shared_ptr graphs — use weak_ptr to break cycles.
  • make_shared is faster than shared_ptr(new T): one allocation instead of two. Downside: object memory is released only when the last weak_ptr goes away too.

Common Mistakes

// 1. Creating two independent shared_ptrs from the same raw pointer
int* raw = new int(1);
std::shared_ptr<int> a(raw);
std::shared_ptr<int> b(raw);  // UB: double free

// 2. Storing this in a shared_ptr without enable_shared_from_this
struct Bad {
    std::shared_ptr<Bad> bad() { return std::shared_ptr<Bad>(this); }  // UB
};

// 3. Dereferencing an expired weak_ptr
auto sp = std::make_shared<int>(1);
std::weak_ptr<int> wp = sp;
sp.reset();
*wp.lock();   // lock() returns empty shared_ptr; dereferencing is UB

// 4. Returning unique_ptr by reference from a container
// Returns reference that may dangle after vector resize
std::unique_ptr<int>& bad_ref = vec[0];  // dangerous

// 5. Forgetting to move when passing to sink
void sink(std::unique_ptr<int> p);
auto p = std::make_unique<int>(1);
sink(p);            // compile error: copy deleted
sink(std::move(p)); // correct