Automatic janitors for your memory β RAII means never saying 'I forgot to delete'
The Janitor Who Never Forgets
Imagine a conference room. People walk in, use the whiteboard, drink coffee. When the last person leaves, a janitor automatically locks the door and cleans up. Nobody has to remember to do it β it just happens.
That's RAII β Resource Acquisition Is Initialization. In C++, smart pointers are that janitor. They own a heap-allocated object, and when the smart pointer goes out of scope, it automatically deletes what it owns. No manual delete. No memory leaks. No double-frees.
Why Raw new/delete Is Dangerous
With raw pointers, you are the janitor. And humans forget.
Forget to delete? Memory leak.
Delete twice? Undefined behavior.
Exception thrown between new and delete? Leak.
Return early from a function? Leak.
Smart pointers solve all of these. Let's meet the three kinds.
unique_ptr β Exclusive Ownership
A std::unique_ptr is like a key to a private locker. Only one unique_ptr can own an object at a time. When it's destroyed, the object is deleted. You can't copy it β but you can move it, transferring ownership like handing the key to someone else.
unique_ptr β Creation and Move
#include <iostream>
#include <memory>
using namespace std;
int main(){
// Create with make_unique (preferred)
auto p1 = make_unique<int>(42);
cout <<"p1 = "<<*p1 << endl;
// Can't copy:
// auto p2 = p1; // ERROR: deleted copy constructor
// But can move:
auto p2 =move(p1);
cout <<"p2 = "<<*p2 << endl;
// p1 is now nullptr
if(!p1) cout <<"p1 is null after move"<< endl;
// Automatically deleted when p2 goes out of scope
return0;
}
Output
p1 = 42
p2 = 42
p1 is null after move
shared_ptr β Shared Ownership
A std::shared_ptr is like a shared Netflix account. Multiple shared_ptrs can point to the same object. Internally, it keeps a reference count. When the last shared_ptr is destroyed, the object is deleted β just like the janitor cleaning up when the last person leaves.
shared_ptr with Reference Counting
#include <iostream>
#include <memory>
using namespace std;
int main(){
auto sp1 = make_shared<string>("Hello, RAII!");
cout <<"sp1: "<<*sp1 << endl;
cout <<"count: "<< sp1.use_count()<< endl;
{
auto sp2 = sp1;// copy β both own the string
cout <<"count after sp2: "<< sp1.use_count()<< endl;
auto sp3 = sp1;// another copy
cout <<"count after sp3: "<< sp1.use_count()<< endl;
}// sp2 and sp3 destroyed here
cout <<"count after block: "<< sp1.use_count()<< endl;
// String deleted when sp1 goes out of scope
return0;
}
Output
sp1: Hello, RAII!
count: 1
count after sp2: 2
count after sp3: 3
count after block: 1
weak_ptr β The Observer
A std::weak_ptr is like a spectator with binoculars β it can see the object a shared_ptr owns, but it doesn't keep it alive. This breaks circular references (where two shared_ptrs point to each other, and neither ever reaches a count of zero).
To use the object, you must call .lock() which gives you a temporary shared_ptr β if the object still exists.
Converting unique_ptr to shared_ptr
#include <iostream>
#include <memory>
using namespace std;
int main(){
// Start with exclusive ownership
auto uniq = make_unique<int>(100);
// Transfer to shared ownership (one-way trip!)
shared_ptr<int> shared =move(uniq);
cout <<"shared: "<<*shared << endl;
cout <<"uniq is null: "<<(uniq == nullptr)<< endl;
// You CANNOT go from shared_ptr back to unique_ptr
// That would violate exclusive ownership!
return0;
}
Output
shared: 100
uniq is null: 1
Custom Deleter
#include <iostream>
#include <memory>
using namespace std;
struct Connection{
string name;
Connection(string n):name(n){
cout <<"Opening "<< name << endl;
}
};
voidcloseConnection(Connection* c){
cout <<"Closing "<< c->name << endl;
delete c;
}
int main(){
// Custom deleter: runs closeConnection instead of plain delete
unique_ptr<Connection,decltype(&closeConnection)>
conn(newConnection("DB-1"), closeConnection);
cout <<"Using "<< conn->name << endl;
// closeConnection called automatically at scope exit
return0;
}
Output
Opening DB-1
Using DB-1
Closing DB-1
Factory Function Returning unique_ptr
#include <iostream>
#include <memory>
#include <string>
using namespace std;
classShape{
public:
virtual string name()const=0;
virtual ~Shape()=default;
};
classCircle:publicShape{
public:
string name()const override {return"Circle";}
};
classSquare:publicShape{
public:
string name()const override {return"Square";}
};
// Factory: caller gets ownership via unique_ptr
unique_ptr<Shape>makeShape(const string& type){
if(type =="circle")return make_unique<Circle>();
if(type =="square")return make_unique<Square>();
return nullptr;
}
int main(){
auto s1 =makeShape("circle");
auto s2 =makeShape("square");
if(s1) cout << s1->name()<< endl;
if(s2) cout << s2->name()<< endl;
// Both automatically cleaned up
return0;
}
Output
Circle
Square
Quick Reference
Smart Pointer
Ownership
Copyable?
Use When
unique_ptr
Exclusive (1 owner)
No (move only)
Default choice. Single owner.
shared_ptr
Shared (N owners)
Yes
Multiple parts of code share one resource
weak_ptr
Observer (0 owners)
Yes
Breaking cycles, caches, optional access
Note: NEVER use raw new/delete in modern C++. Use make_unique and make_shared β they're exception-safe and cleaner. If you're writing 'new', you're probably doing it wrong. The only exception is when you need a custom deleter with unique_ptr, where you must use 'new' directly.
Challenge
Quick check
What happens when you try to copy a std::unique_ptr?