C++ Memory Management Quiz: RAII and Smart Pointers Practice
Test your C++ memory management with 14 questions and three programs on the stack and heap, new and delete, RAII, smart pointers and memory-error tools.
- Course: C++ study plan
- Module: Memory, ownership and RAII
- Kind: Checkpoint — cleared at 70%
- Reading time: 25 min
- Runtime: Clang 18 · C++20
Checkpoint — Memory, ownership and RAII is the checkpoint that closes the Memory, ownership and RAII module: a graded quiz and whole-program exercises, passed at 70%.
Instructions
This checkpoint covers the whole module: the four storage durations and the stack-frame picture, new/delete and the ownership contract, RAII and the guarantees behind it, std::unique_ptr, std::shared_ptr with std::weak_ptr, and the memory-error catalogue with the tools that find it.
How it works. Fourteen questions and three programs. You need 70% on the questions and every program accepted to clear the module. You can retake it as often as you like; your best score counts.
Before you start, make sure you can answer these from memory:
- In what order are three automatic objects declared in one block destroyed, and does an early
returnchange it? - What is undefined about
deleteon a block that came fromnew[]? - What runs between a
throwand thecatchthat handles it? - What does
std::moveleave in astd::unique_ptr, and can you test it? - Why does a
weak_ptrbreak ashared_ptrcycle, and what doeslock()return when the object is gone? - Which three stacks does an AddressSanitizer use-after-free report show?
The three programs are a growable stack that owns its array with new[]/delete[], a ledger of std::shared_ptr handles whose use_count you print, and a task pipeline where a scope guard and a factory's unique_ptr must both behave on the exception path. Every destructor message is in a defined order — trace it on paper before you run.
Common questions
What does std::move leave in a std::unique_ptr?
A null pointer. After auto b = std::move(a); ownership has passed to b, and a is guaranteed to be empty — a defined, testable state, so if (a) is false, and dereferencing a would be undefined behaviour.
What runs between a throw and the catch that handles it?
Stack unwinding: every automatic object in every frame being abandoned is destroyed, in reverse order of construction, before the handler runs. Destructors therefore print their messages before the catch block does, which is what makes RAII cleanup work on the exception path.
What does lock() on a weak_ptr return when the object is gone?
An empty std::shared_ptr. A weak_ptr does not keep its object alive, so once the last owning shared_ptr is destroyed, lock() returns an empty pointer that tests false, and expired() returns true. Always test the result of lock() before using it.
Which three stacks does an AddressSanitizer use-after-free report show?
Where the bad access happened, where the memory was freed, and where it was originally allocated. Together they tell the whole story of a use-after-free: which owner released the block and which borrower kept using it afterwards.
Exercises
A growable stack over raw storage
Write IntStack, a stack that owns an int array obtained with new int[capacity], starting at capacity 2. When a push finds the array full, allocate one of double the capacity, copy the elements across, release the old block with delete[], and print grow <old> -> <new>. The destructor releases the block and prints released capacity <capacity>. Copying is deleted.
Commands, one per line: push <v>; pop (print popped <v> or empty); top (print top=<v> or empty); size (print size=<k>); print (print stack: <bottom> … <top> or stack: (empty)). Popping never shrinks the array.
Input: commands. Output: the command output, the grow lines as they happen, and the destructor's line last.
push 1
push 2
push 3
size
print
pop
pop
top
pop
pop
prints
grow 2 -> 4
size=3
stack: 1 2 3
popped 3
popped 2
top=1
popped 1
empty
released capacity 4A ledger of shared handles
Named handles own resources through std::shared_ptr; several handles may share one resource. Resource has a name and a destructor that prints freed <name>. Keep the handles in a std::map<std::string, std::shared_ptr<Resource>> and process commands:
create <handle> <resource>— if the handle exists printhandle <handle> in use; otherwisestd::make_sharedthe resource and print<resource> owners=<use_count>;alias <new> <existing>— ifexistingis unknown printunknown handle <existing>; ifnewis taken printhandle <new> in use; otherwise copy the pointer and print<resource> owners=<use_count>;release <handle>— if unknown printunknown handle <handle>; otherwise erase it: when other owners remain print<resource> owners=<remaining>, and when it was the last owner the destructor'sfreedline is the only output.
When the input ends, release every remaining handle in alphabetical order of handle name, with exactly the output an explicit release would give — so every resource is freed before main returns and the order is defined.
Input: commands. Output: one line per command, then the end-of-input releases.
create a disk
alias b a
create c net
release a
release b
release c
prints
disk owners=1
disk owners=2
net owners=1
disk owners=1
freed disk
freed netA pipeline on the exception path
Combine a scope guard with a unique_ptr factory and watch both on the exception path. ScopeGuard prints start <id> in its constructor and cleanup <id> in its destructor. Write:
std::unique_ptr<Result> run_task(const std::string& id, long long value)— declare aScopeGuardforid; ifvalueis negativethrow std::invalid_argument("negative value for " + id); otherwise return aResultmade withstd::make_uniqueholdingidandvalue * value;long long store(std::unique_ptr<Result> result)— a sink: printstored <id> -> <square>and return the square.
main reads n tasks. For each, inside a try, call run_task and pass the result to store with std::move, adding the square to a total; catch the std::invalid_argument and print error: <what()>. Finally print results=<stored> total=<sum>. The guard's cleanup line must come before both stored (the guard dies when run_task returns) and error (unwinding runs it before the handler).
Input: n, then n lines of <id> <value> (|value| ≤ 1 000 000). Output: three lines per task, then the summary.
3
a 3
b -2
c 10
prints
start a
cleanup a
stored a -> 9
start b
cleanup b
error: negative value for b
start c
cleanup c
stored c -> 100
results=2 total=109In this module: Memory, ownership and RAII
- The memory model — where objects live and when they die
- new and delete — dynamic memory and the ownership contract
- RAII — a resource's lifetime is an object's lifetime
- std::unique_ptr — exclusive ownership, enforced by the type
- std::shared_ptr and std::weak_ptr — counted ownership and its cycles
- Memory errors and the tools that find them
- Checkpoint — Memory, ownership and RAII (this lesson)
← Memory errors and the tools that find them · Structs and classes — a type of your own →