Modern C++
Smart Pointers
Use modern C++ smart pointers to manage memory safely and automatically.
The Problem with Raw Pointers
Manual memory management with new and delete is error-prone:
- Forget to delete → memory leak
- Delete twice → undefined behavior
- Use after delete → use-after-free bug
Smart Pointers (C++11)
Smart pointers automatically manage memory using RAII (Resource Acquisition Is Initialization):
unique_ptr: Sole ownership, automatically deleted when out of scopeshared_ptr: Shared ownership, deleted when last owner goes out of scopeweak_ptr: Non-owning reference to ashared_ptr(breaks circular references)
The Rule: Prefer Smart Pointers
In modern C++, you should rarely use raw new/delete. Use smart pointers instead.
Example
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Resource {
string name;
public:
Resource(const string& n) : name(n) {
cout << "Acquired: " << name << endl;
}
~Resource() {
cout << "Released: " << name << endl;
}
void use() { cout << "Using: " << name << endl; }
};
void demonstrateUniquePtr() {
// unique_ptr - sole ownership
unique_ptr<Resource> res = make_unique<Resource>("FileHandle");
res->use();
// Transfer ownership with move
unique_ptr<Resource> res2 = move(res);
// res is now null
if (!res) cout << "res is empty after move" << endl;
res2->use();
// Automatically deleted when res2 goes out of scope
}
void demonstrateSharedPtr() {
shared_ptr<Resource> r1 = make_shared<Resource>("SharedDB");
cout << "Count: " << r1.use_count() << endl; // 1
{
shared_ptr<Resource> r2 = r1; // shared ownership
cout << "Count: " << r1.use_count() << endl; // 2
r2->use();
} // r2 destroyed, count goes to 1
cout << "Count: " << r1.use_count() << endl; // 1
} // r1 destroyed, Resource released
int main() {
cout << "--- unique_ptr ---" << endl;
demonstrateUniquePtr();
cout << "--- shared_ptr ---" << endl;
demonstrateSharedPtr();
// unique_ptr in containers
vector<unique_ptr<Resource>> resources;
resources.push_back(make_unique<Resource>("Res1"));
resources.push_back(make_unique<Resource>("Res2"));
for (auto& r : resources) r->use();
// All automatically freed when vector is destroyed
return 0;
}#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Resource {
string name;
public:
Resource(const string& n) : name(n) {
cout << "Acquired: " << name << endl;
}
~Resource() {
cout << "Released: " << name << endl;
}
void use() { cout << "Using: " << name << endl; }
};
void demonstrateUniquePtr() {
// unique_ptr - sole ownership
unique_ptr<Resource> res = make_unique<Resource>("FileHandle");
res->use();
// Transfer ownership with move
unique_ptr<Resource> res2 = move(res);
// res is now null
if (!res) cout << "res is empty after move" << endl;
res2->use();
// Automatically deleted when res2 goes out of scope
}
void demonstrateSharedPtr() {
shared_ptr<Resource> r1 = make_shared<Resource>("SharedDB");
cout << "Count: " << r1.use_count() << endl; // 1
{
shared_ptr<Resource> r2 = r1; // shared ownership
cout << "Count: " << r1.use_count() << endl; // 2
r2->use();
} // r2 destroyed, count goes to 1
cout << "Count: " << r1.use_count() << endl; // 1
} // r1 destroyed, Resource released
int main() {
cout << "--- unique_ptr ---" << endl;
demonstrateUniquePtr();
cout << "--- shared_ptr ---" << endl;
demonstrateSharedPtr();
// unique_ptr in containers
vector<unique_ptr<Resource>> resources;
resources.push_back(make_unique<Resource>("Res1"));
resources.push_back(make_unique<Resource>("Res2"));
for (auto& r : resources) r->use();
// All automatically freed when vector is destroyed
return 0;
}$ g++ main.cpp -o main && ./mainWhere You'll See This in the Real World
Smart pointers answer a question every long-running program has to answer: who is responsible for destroying this, and when. Different answers show up in different parts of real systems:
- One clear owner — a window owns its widgets, a document owns its pages, a connection owns its buffer. When the owner goes, the owned thing goes with it. This is the common case and the reason unique_ptr is the default.
- Shared ownership with an unpredictable last user — a texture used by several models, a config read by several threads, a parsed file held by several requests. Nobody knows who finishes last, so the object is destroyed when the final holder lets go.
- Observing without owning — a cache that should not keep an entry alive on its own, or a child that refers back to its parent. Two objects that hold each other with shared ownership will never be destroyed at all; the non-owning reference is what breaks that cycle.
The detail that bites in production is that reference counting is not free: every copy of a shared pointer touches an atomic counter, and passing one by value in a hot loop is a measurable cost for ownership nobody wanted. Real code passes a reference to the object, and reserves the shared pointer for the moment ownership genuinely changes hands.