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:
| 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