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:
| Type | Ownership | Share? | Overhead |
|---|---|---|---|
unique_ptr<T> | sole owner | No (move only) | Zero — same cost as raw pointer |
shared_ptr<T> | shared owners | Yes (ref-counted) | Reference count (atomic), control block |
weak_ptr<T> | non-owning observer | observes shared_ptr | Small (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::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_ptrhas zero overhead over a raw pointer in optimized builds.shared_ptrhas two overhead sources: an atomic reference count (increment/decrement) and an extra pointer to the control block.- Prefer
unique_ptrand convert toshared_ptronly when sharing is actually needed. - Avoid cyclic
shared_ptrgraphs — useweak_ptrto break cycles. make_sharedis faster thanshared_ptr(new T): one allocation instead of two. Downside: object memory is released only when the lastweak_ptrgoes 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