Most variables you've created so far live on the stack: they're created when a function or block starts and destroyed automatically when it ends. That's fast and safe, but sometimes an object needs to outlive the block that created it, or you don't know until the program runs how many objects you'll need. Those objects live on the heap, also called dynamic memory.
In older C++, heap memory was managed by hand with new and delete. Forgetting a delete leaks memory, deleting twice crashes the program, and using memory after it's been deleted causes some of the hardest bugs in software. Modern C++ solves this with smart pointers: objects that own heap memory and release it automatically.
In this lesson you'll see how stack and heap differ, why raw new and delete are risky, and how std::unique_ptr and std::shared_ptr make ownership clear and memory leaks almost impossible. It builds on the pointers lesson.
Stack, heap, new and delete
void demo() {
int a = 5; // stack: freed automatically
int* b = new int(7); // heap: lives until deleted
delete b; // must free it yourself
}newcreates an object on the heap and returns a pointer to it.deletedestroys it and frees the memory. Arrays usenew[]anddelete[].- Forget the
deleteand the memory is leaked until the program ends. - Use the pointer after
deleteand you have a dangling pointer: undefined behaviour.
Getting every delete right is hard, especially when functions can return early or throw exceptions. That's why modern C++ code almost never writes new and delete directly.
RAII and std::unique_ptr
RAII (Resource Acquisition Is Initialisation) means tying a resource to an object's lifetime: the constructor acquires it and the destructor releases it. You've already used RAII with vector, string and ofstream.
std::unique_ptr applies RAII to heap objects. It's the only owner of the object it points to, and deletes it automatically when the unique_ptr itself is destroyed:
#include <memory>
auto player = make_unique<Player>("Ana"); // create on the heap
player->score = 10; // use it like a pointer
// no delete neededmake_unique<T>(args...) creates the object and the unique_ptr in one step. Use it instead of new.
Moving ownership
A unique_ptr can't be copied, because two owners would both try to delete the same object. Ownership can be moved instead with std::move:
auto a = make_unique<int>(5);
auto b = move(a); // b owns the int now; a is empty (nullptr)That's how you transfer an object into a function, return it from a function or put it into a container: vector<unique_ptr<Shape>> holds objects of different derived classes and deletes them all when the vector is destroyed, exactly as in the inheritance lesson.
C++ Smart Pointers Syntax
Smart pointer syntax
#include <memory>
auto u = make_unique<Type>(args);
u->member; *u; u.get();
auto u2 = move(u); // transfer ownership
auto s = make_shared<Type>(args);
auto s2 = s; // shared ownership
s.use_count();
vector<unique_ptr<Base>> items;
items.push_back(make_unique<Derived>(args));C++ Smart Pointers Summary
- Stack variables are freed automatically; heap objects live until freed.
- Raw
newanddeleterisk leaks, double deletes and dangling pointers. - RAII ties resources to object lifetimes; smart pointers apply it to heap memory.
unique_ptris a single owner, created withmake_unique, moved withstd::move.shared_ptrcounts owners and deletes at zero;weak_ptrobserves without owning.
C++ Smart Pointers Examples
Each example shows the code, the output it produces and what each part does.
unique_ptr cleans up automatically
#include <iostream>
#include <memory>
#include <string>
using namespace std;
class Player {
string name;
public:
Player(const string& n) : name(n) { cout << name << " joined" << endl; }
~Player() { cout << name << " left" << endl; }
void play() const { cout << name << " is playing" << endl; }
};
int main() {
auto ana = make_unique<Player>("Ana");
{
auto ben = make_unique<Player>("Ben");
ben->play();
}
ana->play();
cout << "End of main" << endl;
return 0;
}Ana joined
Ben joined
Ben is playing
Ben left
Ana is playing
End of main
Ana leftLine-by-line explanation
- Line 15make_unique creates Ana on the heap and returns the owning pointer.
- Lines 17 to 18Ben's unique_ptr lives only inside this block.
- Line 19At the closing brace, the unique_ptr is destroyed and deletes Ben automatically.
- Line 23Ana is deleted when main ends. No delete was written anywhere.
Moving ownership into a container
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
struct Task {
string title;
Task(const string& t) : title(t) {}
};
int main() {
vector<unique_ptr<Task>> tasks;
auto first = make_unique<Task>("Write report");
tasks.push_back(move(first));
tasks.push_back(make_unique<Task>("Email team"));
cout << "first is now " << (first ? "full" : "empty") << endl;
for (const auto& t : tasks) {
cout << "- " << t->title << endl;
}
return 0;
}first is now empty
- Write report
- Email teamLine-by-line explanation
- Line 15push_back(first) wouldn't compile, because unique_ptr can't be copied; move transfers ownership.
- Line 16A temporary from make_unique can be moved in directly.
- Line 18After the move, first holds nullptr.
- Lines 19 to 21The vector owns both tasks and deletes them when it's destroyed.
shared_ptr and the reference count
#include <iostream>
#include <memory>
#include <string>
using namespace std;
struct Settings {
string theme = "dark";
~Settings() { cout << "Settings deleted" << endl; }
};
int main() {
auto s1 = make_shared<Settings>();
cout << "Owners: " << s1.use_count() << endl;
{
auto s2 = s1;
s2->theme = "light";
cout << "Owners: " << s1.use_count() << ", theme " << s1->theme << endl;
}
cout << "Owners: " << s1.use_count() << endl;
s1.reset();
cout << "After reset" << endl;
return 0;
}Owners: 1
Owners: 2, theme light
Owners: 1
Settings deleted
After resetLine-by-line explanation
- Line 15Copying a shared_ptr adds an owner; both point to the same Settings.
- Lines 16 to 17A change through s2 is visible through s1.
- Line 18s2 is destroyed, so the count drops back to 1.
- Line 20reset releases the last owner, so the object is deleted immediately.
Common C++ Smart Pointers Mistakes
Forgetting delete
void load() {
Player* p = new Player("Ana");
if (error) return; // leak: p is never deleted
delete p;
}void load() {
auto p = make_unique<Player>("Ana");
if (error) return; // p is deleted automatically
}Copying a unique_ptr
auto a = make_unique<int>(5);
auto b = a; // error: copy constructor is deletedauto a = make_unique<int>(5);
auto b = move(a); // a is now emptyUsing shared_ptr everywhere
shared_ptr<Player> p = make_shared<Player>("Ana"); // only one owner everunique_ptr<Player> p = make_unique<Player>("Ana");shared_ptr adds counting overhead and hides who really owns the object. Use it only for genuinely shared ownership.
C++ Smart Pointers in Real Projects
Smart pointers in modern C++
Modern codebases at companies such as Google and Microsoft discourage raw new and delete in their style guides. Game engines own their entities through unique_ptr, GUI frameworks share resources like textures with shared_ptr, and tools such as AddressSanitizer exist mainly to catch the memory bugs that smart pointers prevent.
C++ Smart Pointers FAQ
What do reset and release do on a unique_ptr?
p.reset() deletes the object the pointer owns and leaves p empty; p.reset(new Thing) swaps in a new object after deleting the old one. p.release() gives up ownership without deleting: it returns the raw pointer and leaves p empty, so you're responsible for deleting the object yourself. You'll rarely need release outside code that talks to older APIs.
Can a unique_ptr manage an array?
Yes: auto nums = make_unique<int[]>(100); creates 100 ints, and nums[0] indexes them. The [] in the type makes it call delete[] correctly when it's destroyed. A vector<int> is usually a better choice, though, because it knows its size and can grow.
Are smart pointers slower than raw pointers?
A unique_ptr is normally the same size as a raw pointer and costs essentially nothing extra; it simply calls delete for you at the right time. A shared_ptr is bigger, typically two pointers, and every copy updates a reference count with a thread-safe atomic operation. That overhead is small, but it's another reason to prefer unique_ptr unless ownership is truly shared.
Can I convert a unique_ptr into a shared_ptr?
Yes, by moving it: shared_ptr<Player> shared = std::move(unique);. The unique_ptr is left empty and the shared_ptr takes over the object. The reverse isn't possible, because a shared_ptr can't be sure it's the only owner. That's why factory functions often return unique_ptr: callers can keep it unique or turn it into a shared one.
How do I use a smart pointer with a C resource like FILE*?
Give unique_ptr a custom deleter, the function to call instead of delete: unique_ptr<FILE, int(*)(FILE*)> file(fopen("log.txt", "w"), fclose);. When file goes out of scope, fclose runs automatically, even if an exception is thrown. The same trick works for any C library handle that has a matching "close" or "free" function.
C++ Smart Pointers Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the line that creates a heap object owned by a unique_ptr.
auto p = <int>(42);
What does a unique_ptr do when it's destroyed?
What does this print?
auto a = make_shared<int>(1);
auto b = a;
auto c = b;
cout << a.use_count();
How do you transfer ownership from one unique_ptr to another?
What problem does weak_ptr solve?
C++ Smart Pointers Quiz
5 questions to check what you learned.
Where do objects created with
newlive?- On the heap
- On the stack
- In a file
- In the CPU cache only
What is a memory leak?
- Reading a file twice
- A pointer to a stack variable
- Heap memory that's never freed after it's no longer needed
- A stack overflow
What does RAII mean in practice?
- Always use raw pointers
- A resource is acquired in a constructor and released in the destructor
- Allocate everything at program start
- Never use classes
Which smart pointer should you choose by default?
- shared_ptr
- weak_ptr
- unique_ptr
- auto_ptr
When is an object managed by shared_ptr deleted?
- When the first shared_ptr is destroyed
- At the end of main only
- Never
- When the last shared_ptr owning it is destroyed or reset