Step 1 of 7 · Reading · ~4 min
Learn
Pointers and Memory
Smart Pointers: unique_ptr, shared_ptr and weak_ptr
You will pick the right owner type for an object, hand ownership between functions, and observe a shared object without keeping it alive. Last lesson every new needed a hand-written delete and you met four ways that fails; here the compiler writes the delete.
RAII: the scope owns the object
A smart pointer is a small object whose destructor does the delete. Destructors run when a scope ends, on every path out (early return, exception), so cleanup cannot be forgotten. Tying a resource to an object's lifetime is called RAII.
struct Sensor {
std::string id;
explicit Sensor(std::string i) : id(i) { std::cout << "on " << id << "\n"; }
~Sensor() { std::cout << "off " << id << "\n"; }
};
void probe() {
auto s = std::make_unique<Sensor>("t1");
std::cout << "reading " << s->id << "\n";
} // s dies here, taking the Sensor with it
probe() prints on t1, reading t1, off t1.
unique_ptr: exactly one owner
std::unique_ptr<T> cannot be copied, so two owners are impossible by construction:
auto a = std::make_unique<Sensor>("t2");
auto b = a; // does not compile
Clang says call to deleted constructor of 'std::unique_ptr<...>'. To hand the object over, spell it std::move:
auto b = std::move(a);
std::cout << (a ? "a owns" : "a empty"); // a empty
std::move(a) hands the pointer over and leaves a null (the move-semantics lesson explains how).
b.get()returns the raw pointer to borrow;bstill owns it.b.reset()deletes now and leavesbnull.b.release()gives up ownership without deleting; the caller mustdeletethe result.std::make_unique<int[]>(4)owns four zeroed ints and callsdelete[]for you.
The signature tells the reader who owns what:
void show(const Sensor& s); // borrows
void inspect(const Sensor* s); // borrows; may be null
void adopt(std::unique_ptr<Sensor> s); // takes ownership: adopt(std::move(p))
std::unique_ptr<Sensor> make(std::string); // hands ownership to the caller
A vector of unique_ptr owns a mixed family; the base needs the virtual destructor you already have:
struct Shape { virtual ~Shape() = default; virtual int area() const = 0; };
struct Sq : Shape { int s; Sq(int s) : s(s) {} int area() const override { return s * s; } };
struct Rect : Shape { int w, h; Rect(int w, int h) : w(w), h(h) {} int area() const override { return w * h; } };
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Sq>(2));
shapes.push_back(std::make_unique<Rect>(3, 5));
for (const auto& p : shapes) std::cout << p->area() << "\n"; // 4 then 15
Writing auto p in that loop copies each unique_ptr and fails to compile.
shared_ptr: counted owners
When several owners are genuinely needed, std::shared_ptr<T> keeps a count in a control block (make_shared allocates it together with the object). Each copy adds one, each destruction removes one, and the object dies at zero.
auto x = std::make_shared<Sensor>("t3"); // on t3
auto y = x;
std::cout << x.use_count(); // 2
x.reset(); // count 1, nothing printed
y.reset(); // count 0: off t3
A returned unique_ptr converts to shared ownership: std::shared_ptr<Sensor> s = make("t4");. The reverse is impossible.
weak_ptr: observe without owning
std::weak_ptr<T> watches a shared object but does not count. expired() says whether the object is gone, and lock() returns a shared_ptr that is empty if so.
auto z = std::make_shared<Sensor>("t5");
std::weak_ptr<Sensor> w = z; // z owns; count stays 1
auto g = w.lock(); // g is a second owner while it lives
std::cout << z.use_count(); // 2, not 1
Weak pointers break cycles. Two Peers holding shared_ptr<Peer> to each other keep each other alive: leaving the scope prints nothing and both leak. Keep the forward link shared_ptr<Peer> next, make the back link weak_ptr<Peer> prev, and the counts are 1 and 2; scope exit prints free p then free q.
Common mistakes
- Two owners from one raw pointer:
std::shared_ptr<Sensor> a(raw), b(raw);makes two control blocks (both counts read 1), and the second delete aborts withdouble free detected. Copy the shared_ptr instead. - Counting the lock:
w.lock().use_count()reads one too many, because the pointer you just locked is itself an owner. - Using a moved-from or released unique_ptr: it is null, so
->crashes.
Your exercise
A script of commands opens, shares, drops and observes named resources whose constructor and destructor print, so lifetimes show in the output. Make close print exactly when the last owner lets go. The likeliest mistake is testing a name with handles[name], which silently inserts an empty entry.
Discussion
Ask a question, share an insight, or help someone who’s stuck.
Sign in to post a comment or reply.
Loading…