class: center, middle # CMPE 30: Lecture 18 Pointers, new/delete, and unique_ptr --- # What does this print? .lc[ ```cpp class Counter { int n = 0; public: Counter &add() { ++n; return *this; } int get() const { return n; } }; Counter c; c.add().add().add(); std::cout << c.get(); ``` ] .rc[ 1. 0 1. 1 1. 3 1. compile error 1. Ben got this wrong ] --- # Learning Objectives - Distinguish stack from heap memory - Declare and dereference a pointer; use `->` - Allocate and free heap memory with `new` / `delete` - Recognize memory leaks and dangling pointers - Use `std::unique_ptr` with `std::make_unique` as the default - Explain RAII --- # Stack vs Heap .lc[ ```cpp void play() { int volume = 11; // stack } // volume destroyed ``` ] .rc[ **Stack** --- fast, automatic, scoped. **Heap** --- manually allocated, persists until you free it. - Stack: coat check - Heap: storage unit lease **Tip:** Prefer the stack. Use the heap only when you must. ] --- # Pointer Basics .lc[ ```cpp int volume = 11; int *ptr = &volume; std::cout << &volume; // 0x7ffd3a2c std::cout << *ptr; // 11 *ptr = 5; std::cout << volume; // 5 ``` ] .rc[ - `&` is the **address-of** operator - `int *ptr` declares "pointer to int" - `*ptr` **dereferences** the pointer **Wut:** `*` has three meanings: pointer declaration, dereference, or multiplication. Context decides. ] --- # The Arrow Operator .lc[ ```cpp struct Song { std::string title; int year; }; Song s = {"Popular", 1996}; Song *ptr = &s; (*ptr).title; // awkward ptr->title; // equivalent, clean ``` ] .rc[ - `ptr->member` is exactly `(*ptr).member` - Everywhere you have a pointer to a struct or class - The standard way in C++ ] --- # Why the Heap? .lc[ ```cpp int count; std::cin >> count; // int scores[count]; // not C++ std::string *make() { std::string local = "Don't Speak"; return &local; // BUG: dangling } ``` ] .rc[ - Size not known until runtime - Object must outlive the current scope The heap solves both. ] --- # nullptr .lc[ ```cpp int *ptr = nullptr; if (ptr != nullptr) { std::cout << *ptr; } ``` ] .rc[ - A pointer that points to nothing - Dereferencing `nullptr` is undefined behavior (a crash) - Always check pointers you are not sure about ] --- # new and delete .lc[ ```cpp std::string *song = new std::string("Under the Bridge"); std::cout << *song << "\n"; delete song; // song still exists as a pointer, // but memory is freed ``` ] .rc[ - `new` allocates on the heap, returns a pointer - `delete` frees the memory - Using the pointer after `delete` is **undefined behavior** ] --- # new[] and delete[] .lc[ ```cpp int *scores = new int[5]; scores[0] = 10; scores[1] = 20; // ... delete[] scores; ``` ] .rc[ - `new[]` allocates an array - `delete[]` frees an array **Trap:** `new` pairs with `delete`, `new[]` with `delete[]`. Mixing them is undefined behavior --- the compiler will not warn you. ] --- # Memory Leaks .lc[ ```cpp void leak() { std::string *s = new std::string( "Nothing Compares 2 U"); // oops --- never delete s } ``` ] .rc[ - Every call allocates and never frees - Eventually runs out of memory - The #1 problem with raw `new`/`delete` ] --- # Dangling Pointers .lc[ ```cpp int *p = new int(42); delete p; std::cout << *p; // DANGER: p is dangling ``` ] .rc[ - Dereferencing freed memory is undefined behavior - Might crash, might print garbage, might appear to work - The #2 problem with raw `new`/`delete` ] --- # Smart Pointers and RAII **RAII** --- Resource Acquisition Is Initialization. Resources are acquired in a constructor and released in a destructor. Lifetimes are tied to objects. A **smart pointer** owns heap memory and automatically frees it when destroyed. Smart pointers live in `
`. --- # std::unique_ptr .lc[ ```cpp #include
#include
auto song = std::make_unique< std::string>("Don't Speak"); std::cout << *song << "\n"; // memory freed automatically // when `song` goes out of scope ``` ] .rc[ - **Sole ownership**: only one `unique_ptr` can own the memory - Zero overhead vs a raw pointer - Use `std::make_unique` --- always prefer over raw `new` ] --- # unique_ptr Cannot Be Copied .lc[ ```cpp auto a = std::make_unique
(42); // ERROR: cannot copy // auto b = a; // OK: move auto c = std::move(a); // a is now empty ``` ] .rc[ - Copying would mean two owners --- both would try to free - **Moving** transfers ownership instead - More on `std::move` next lecture **Tip:** `unique_ptr` should be your default for heap allocation. ] --- # What is wrong with this code? .lc[ ```cpp void play() { int *volumes = new int[3]; volumes[0] = 7; volumes[1] = 9; volumes[2] = 11; delete volumes; } ``` ] .rc[ 1. Missing `
` 1. `new int[3]` is invalid 1. `delete` should be `delete[]` 1. `volumes` must be `const` 1. Ben got this wrong ] --- # Why can you not copy a unique_ptr? 1. Copies are slow 1. Sole ownership --- two owners would both try to free the memory 1. The compiler has a bug 1. `unique_ptr` is not a real type 1. Ben got this wrong --- # Key Points - Stack is automatic, heap is manual; prefer stack - `&` = address-of, `*` = dereference, `->` = member through pointer - `new` pairs with `delete`; `new[]` with `delete[]` - Raw `new`/`delete` in modern C++ is a red flag - `std::unique_ptr` + `std::make_unique` = sole ownership with cleanup **Read:** chapter 13, remaining sections --- `shared_ptr`, `.get()`, move semantics. **Do:** exercises 2, 4, 5, 6, 7, 8.