Bank 9 — RAII and smart pointers¶
Twelve exercises on tying a resource's lifetime to an object's lifetime, and on
unique_ptr, shared_ptr and weak_ptr. Module 11. Levels L1–L6.
Solutions are in Solutions and answers; nudges are in Hints. Attempt first.
Exercises¶
E94 — What RAII actually means L1 concept¶
Task. Expand the acronym and define it in one sentence. Then say what each of these three has to do with RAII: a constructor, a destructor, and stack unwinding during an exception. Finally: name one resource other than memory that RAII manages.
E95 — Out of scope L2 predict¶
#include <iostream>
#include <memory>
struct Res {
const char* name;
explicit Res(const char* n) : name(n) { std::cout << '+' << name << ' '; }
~Res() { std::cout << '-' << name << ' '; }
};
int main() {
std::cout << "a ";
{
auto p = std::make_unique<Res>("x");
std::cout << "b ";
}
std::cout << "c\n";
}
Task. Write the single line of output. Then say which line of your program caused
-x to be printed, and why no explicit delete appears anywhere.
E96 — Say unique_ptr instead L2 complete memory¶
#include <iostream>
#include <memory>
class Logger {
public:
explicit Logger(int id) : id_(id) { std::cout << "open " << id_ << '\n'; }
~Logger() { std::cout << "close " << id_ << '\n'; }
private:
int id_;
};
int main() {
Logger* raw = new Logger{7}; // TODO (1): make this owning and automatic
std::cout << "working\n";
// TODO (2): remove the manual cleanup entirely
}
Task. Convert the raw pointer to a std::unique_ptr so the close line is printed
automatically, and delete the manual cleanup. Say what type the make_unique call
returns and why you no longer write delete.
E97 — A shelf of devices L2 trace scenario¶
#include <iostream>
#include <memory>
#include <vector>
struct Device {
int id;
explicit Device(int i) : id(i) { std::cout << "on " << id << ' '; }
~Device() { std::cout << "off " << id << ' '; }
};
int main() {
std::vector<std::unique_ptr<Device>> shelf;
shelf.push_back(std::make_unique<Device>(1));
shelf.push_back(std::make_unique<Device>(2));
shelf.push_back(std::make_unique<Device>(3));
std::cout << "| ";
shelf.clear();
std::cout << "end\n";
}
Task. Write the output. In what order are the devices switched off, and what does
shelf.clear() do to each one?
E98 — Share the connection L3 modify scenario¶
Several widgets need to use one shared database connection, and none of them owns it alone — it closes when the last user is gone.
#include <iostream>
#include <memory>
#include <string>
class Connection {
public:
explicit Connection(std::string url)
: url_(std::move(url)) { std::cout << "connected\n"; }
~Connection() { std::cout << "disconnected\n"; }
private:
std::string url_;
};
void widget(const std::shared_ptr<Connection>& c, const char* name) {
std::cout << name << " uses connection\n";
}
int main() {
auto conn = std::make_shared<Connection>("db://local");
widget(conn, "A");
widget(conn, "B");
std::cout << "use_count " << conn.use_count() << '\n';
}
Task. The program already uses shared_ptr. Explain why it must be shared_ptr
and not unique_ptr here, and what use_count() will print at the marked line. Where
and when is disconnected printed?
E99 — They keep each other alive L3 debug memory¶
A graph uses two-way links.
#include <iostream>
#include <memory>
#include <string>
struct Node {
std::string name;
std::shared_ptr<Node> neighbour;
~Node() { std::cout << "gone " << name << '\n'; }
};
int main() {
auto a = std::make_shared<Node>();
auto b = std::make_shared<Node>();
a->name = "A";
b->name = "B";
a->neighbour = b;
b->neighbour = a;
std::cout << "count a " << a.use_count()
<< ", count b " << b.use_count() << '\n';
}
Task. The program never prints gone A or gone B. Explain the reference cycle,
give the two use_count numbers, and fix it by changing one member to weak_ptr.
Which member would you change, and why does the fix break the cycle?
E100 — Peek without owning L3 predict¶
#include <iostream>
#include <memory>
int main() {
auto sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
if (auto locked = wp.lock())
std::cout << "alive " << *locked << '\n';
std::cout << "count " << sp.use_count() << '\n';
sp.reset();
if (auto locked = wp.lock())
std::cout << "alive\n";
else
std::cout << "gone\n";
}
Task. Write all three output lines. Explain why wp.lock() returns something even
though wp does not keep the int alive, and why you must always check the result
before using it.
E101 — Which pointer? L4 analyze smart¶
For each situation, choose unique_ptr, shared_ptr, weak_ptr or a value, and
give a one-sentence reason:
- a
Librarythat owns its items; - one logger shared by several components, closed when the last is gone;
- an observer that watches a subject but must not keep it alive;
- a small
Pointheld by aShape; - a cached object that can expire and be rebuilt.
Task. Then answer: why is shared_ptr considered a last resort rather than the
default owning pointer?
E102 — Used after it was moved L4 debug scenario¶
#include <iostream>
#include <memory>
struct Job {
int id;
explicit Job(int i) : id(i) {}
};
int main() {
auto job = std::make_unique<Job>(5);
auto moved = std::move(job);
std::cout << "owner id " << moved->id << '\n';
std::cout << "old id " << job->id << '\n'; // line A
}
Task. Explain the state of job after the std::move, and why line A is a bug
even though it compiles. Say what "moved-from" means for a unique_ptr specifically,
and give the two safe patterns for handling it (a check, and a restructuring).
E103 — Design a session guard L5 design smart scenario¶
A device farm opens a session before work and must close it afterwards, including when the work throws. A second requirement: two jobs must not be able to hold the same session at once, but a job may hand its session to another job.
Task. Design the guard. Give:
- the class name, and what its constructor and destructor do;
- which of the five special members you delete and which you keep, with one sentence each — this is the heart of the exercise;
- how the caller uses it (three lines of pseudo-code);
- one sentence on why this is stronger than a
close()the caller must remember.
How you will be judged. On the special-members decision: forbid copying, allow moving, and say why each.
E104 — Retire the raw pointers L6 refactor smart scenario¶
A module manager owns subsystems with new/delete, passes some of them around by
raw pointer, and sometimes double-deletes. Nobody is sure which module owns which
subsystem.
Task. Write a refactoring plan. It must:
- classify every raw pointer as owner, borrower, or shared — with a rule you can apply mechanically;
- say which pointer type replaces each class of pointer;
- identify one place where the ownership is genuinely shared and cannot be made unique, and how you would decide it is shared;
- state, for the locked design, where each subsystem is destroyed.
Success. Every allocation has exactly one owner, or a documented reason it is shared.
E105 — A deleter for something else L3 modify smart¶
Resources are not always freed with delete.
#include <cstdio>
#include <iostream>
#include <memory>
struct FileCloser {
void operator()(std::FILE* f) const {
if (f) { std::fclose(f); std::cout << "closed\n"; }
}
};
int main() {
// std::FILE* is opened with fopen and closed with fclose, not delete.
{
std::unique_ptr<std::FILE, FileCloser> f{ std::fopen("scratch_bank.txt", "w") };
if (f) std::fputs("hello", f.get());
std::cout << "writing\n";
}
std::cout << "after\n";
}
Task. Run this, then explain how unique_ptr knew to call fclose instead of
delete. What role does FileCloser play, and why must f be checked before use?
(You may run it from a scratch directory; it creates a small file.)
Previous topic: Copying and resource management · Next topic: Templates · Hints: hints.md · Solutions: solutions.md