C++ Cheatsheet

Smart Pointers

Use this C++ reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

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