cpp
Smart Pointers
04_Smart_Pointers⚙️cpp
/**
* ============================================================
* C++ SMART POINTERS
* ============================================================
*
* This file covers:
* - unique_ptr
* - shared_ptr
* - weak_ptr
* - make_unique and make_shared
* - Custom deleters
*
* Compile: g++ -std=c++17 -Wall 01_smart_pointers.cpp -o smart_pointers
* Run: ./smart_pointers
*
* ============================================================
*/
#include <iostream>
#include <memory>
#include <vector>
#include <string>
using namespace std;
// Demo class for demonstrations
class Resource {
private:
string name;
int id;
static int nextId;
public:
Resource(const string& n = "Unnamed") : name(n), id(nextId++) {
cout << " [+] Resource " << id << " (" << name << ") created" << endl;
}
~Resource() {
cout << " [-] Resource " << id << " (" << name << ") destroyed" << endl;
}
void use() const {
cout << " [*] Using Resource " << id << " (" << name << ")" << endl;
}
string getName() const { return name; }
int getId() const { return id; }
};
int Resource::nextId = 1;
// Forward declaration for circular reference demo
class Node;
int main() {
cout << "============================================" << endl;
cout << " C++ SMART POINTERS" << endl;
cout << "============================================" << endl << endl;
// ========================================================
// PART 1: WHY SMART POINTERS?
// ========================================================
cout << "--- PART 1: WHY SMART POINTERS? ---" << endl << endl;
/*
* Smart pointers automatically manage memory:
* - No manual delete needed
* - Exception safe
* - Clear ownership semantics
* - Prevent memory leaks and dangling pointers
*
* Three types in C++11:
* 1. unique_ptr - Exclusive ownership
* 2. shared_ptr - Shared ownership
* 3. weak_ptr - Non-owning observer
*/
cout << "Problems with raw pointers:" << endl;
cout << "• Forget to delete → memory leak" << endl;
cout << "• Delete twice → undefined behavior" << endl;
cout << "• Exception before delete → leak" << endl;
cout << "• Unclear ownership → confusion" << endl;
cout << endl;
cout << "Smart pointers solve these problems!" << endl;
cout << endl;
// ========================================================
// PART 2: unique_ptr
// ========================================================
cout << "--- PART 2: unique_ptr (Exclusive Ownership) ---" << endl << endl;
/*
* unique_ptr:
* - Only ONE pointer can own the resource
* - Cannot be copied (only moved)
* - Automatically deletes when it goes out of scope
* - Zero overhead compared to raw pointer
*/
cout << "Creating unique_ptr:" << endl;
{
// Method 1: Direct construction (older style)
unique_ptr<Resource> ptr1(new Resource("First"));
// Method 2: make_unique (preferred, C++14)
auto ptr2 = make_unique<Resource>("Second");
ptr1->use();
ptr2->use();
cout << "\nptr1 owns: " << (ptr1 ? ptr1->getName() : "nothing") << endl;
cout << "ptr2 owns: " << (ptr2 ? ptr2->getName() : "nothing") << endl;
// Cannot copy unique_ptr
// unique_ptr<Resource> ptr3 = ptr1; // ERROR!
// Can move unique_ptr
cout << "\nMoving ptr1 to ptr3:" << endl;
unique_ptr<Resource> ptr3 = move(ptr1); // Transfer ownership
cout << "ptr1 owns: " << (ptr1 ? ptr1->getName() : "nothing") << endl;
cout << "ptr3 owns: " << (ptr3 ? ptr3->getName() : "nothing") << endl;
// Release ownership (returns raw pointer, you're responsible)
cout << "\nReleasing ptr3:" << endl;
Resource* raw = ptr3.release();
cout << "ptr3 owns: " << (ptr3 ? ptr3->getName() : "nothing") << endl;
delete raw; // Manual delete needed after release!
// Reset (delete current and optionally take new)
cout << "\nResetting ptr2:" << endl;
ptr2.reset(); // Deletes "Second"
ptr2.reset(new Resource("New Second"));
cout << "\nScope ending, automatic cleanup:" << endl;
} // ptr2 automatically deleted here
cout << endl;
// ========================================================
// PART 3: unique_ptr WITH ARRAYS
// ========================================================
cout << "--- PART 3: unique_ptr WITH ARRAYS ---" << endl << endl;
{
// unique_ptr for arrays
auto arr = make_unique<int[]>(5);
for (int i = 0; i < 5; i++) {
arr[i] = (i + 1) * 10;
}
cout << "Array via unique_ptr: ";
for (int i = 0; i < 5; i++) {
cout << arr[i] << " ";
}
cout << endl;
// Automatically calls delete[] when scope ends
}
cout << "\n💡 For dynamic arrays, prefer std::vector!" << endl;
cout << endl;
// ========================================================
// PART 4: shared_ptr
// ========================================================
cout << "--- PART 4: shared_ptr (Shared Ownership) ---" << endl << endl;
/*
* shared_ptr:
* - Multiple pointers can share ownership
* - Uses reference counting
* - Deleted when last shared_ptr is destroyed
* - Slight overhead (reference count)
*/
cout << "Creating shared_ptr:" << endl;
{
// Method 1: Direct (not recommended)
shared_ptr<Resource> sp1(new Resource("Shared1"));
// Method 2: make_shared (preferred)
auto sp2 = make_shared<Resource>("Shared2");
cout << "sp1 use_count: " << sp1.use_count() << endl;
cout << "sp2 use_count: " << sp2.use_count() << endl;
// Copy shared_ptr (increases reference count)
cout << "\nCopying sp2 to sp3:" << endl;
shared_ptr<Resource> sp3 = sp2;
cout << "sp2 use_count: " << sp2.use_count() << endl;
cout << "sp3 use_count: " << sp3.use_count() << endl;
{
cout << "\nCreating sp4 (copy of sp2) in inner scope:" << endl;
shared_ptr<Resource> sp4 = sp2;
cout << "sp2 use_count: " << sp2.use_count() << endl;
}
cout << "After inner scope, sp2 use_count: " << sp2.use_count() << endl;
// Reset one pointer
cout << "\nResetting sp3:" << endl;
sp3.reset();
cout << "sp2 use_count: " << sp2.use_count() << endl;
cout << "\nScope ending:" << endl;
}
cout << endl;
// ========================================================
// PART 5: weak_ptr
// ========================================================
cout << "--- PART 5: weak_ptr (Non-owning Observer) ---" << endl << endl;
/*
* weak_ptr:
* - Does NOT own the resource
* - Does NOT affect reference count
* - Used to break circular references
* - Must lock() to access (returns shared_ptr)
*/
weak_ptr<Resource> weakPtr;
{
cout << "Creating shared_ptr:" << endl;
auto sharedPtr = make_shared<Resource>("Observable");
weakPtr = sharedPtr; // weak_ptr observes
cout << "shared_ptr use_count: " << sharedPtr.use_count() << endl;
cout << "weak_ptr expired: " << boolalpha << weakPtr.expired() << endl;
// Access through weak_ptr
if (auto locked = weakPtr.lock()) {
cout << "Locked weak_ptr, using: ";
locked->use();
cout << "use_count during lock: " << sharedPtr.use_count() << endl;
}
cout << "\nShared_ptr going out of scope:" << endl;
}
cout << "After shared_ptr destroyed:" << endl;
cout << "weak_ptr expired: " << weakPtr.expired() << endl;
if (auto locked = weakPtr.lock()) {
locked->use(); // Won't execute
} else {
cout << "Cannot lock - resource is gone" << endl;
}
cout << noboolalpha << endl;
// ========================================================
// PART 6: CIRCULAR REFERENCE PROBLEM
// ========================================================
cout << "--- PART 6: CIRCULAR REFERENCES ---" << endl << endl;
/*
* Problem: Two shared_ptrs pointing to each other
* Result: Memory leak (neither ever deleted)
* Solution: Use weak_ptr for back-references
*/
cout << "Circular reference causes memory leak:" << endl;
cout << " struct Node {" << endl;
cout << " shared_ptr<Node> next;" << endl;
cout << " shared_ptr<Node> prev; // ← Problem!" << endl;
cout << " };" << endl;
cout << endl;
cout << "Solution - use weak_ptr:" << endl;
cout << " struct Node {" << endl;
cout << " shared_ptr<Node> next;" << endl;
cout << " weak_ptr<Node> prev; // ← Fixed!" << endl;
cout << " };" << endl;
cout << endl;
// ========================================================
// PART 7: make_unique AND make_shared
// ========================================================
cout << "--- PART 7: make_unique & make_shared ---" << endl << endl;
cout << "Why use make_unique/make_shared?" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "1. Exception safety" << endl;
cout << " // Problem:" << endl;
cout << " func(shared_ptr<T>(new T), mayThrow());" << endl;
cout << " // If mayThrow() throws, new T might leak!" << endl;
cout << endl;
cout << " // Solution:" << endl;
cout << " func(make_shared<T>(), mayThrow());" << endl;
cout << " // Safe: make_shared is one operation" << endl;
cout << endl;
cout << "2. Efficiency (make_shared)" << endl;
cout << " // One allocation for object + control block" << endl;
cout << endl;
cout << "3. Cleaner syntax" << endl;
cout << " auto p = make_unique<MyClass>(args...);" << endl;
cout << endl;
// ========================================================
// PART 8: CUSTOM DELETERS
// ========================================================
cout << "--- PART 8: CUSTOM DELETERS ---" << endl << endl;
cout << "Custom deleter for unique_ptr:" << endl;
{
auto deleter = [](Resource* ptr) {
cout << " Custom deleter called!" << endl;
delete ptr;
};
unique_ptr<Resource, decltype(deleter)> ptr(new Resource("Custom"), deleter);
ptr->use();
cout << "Scope ending..." << endl;
}
cout << "\nCustom deleter for shared_ptr:" << endl;
{
auto ptr = shared_ptr<Resource>(
new Resource("SharedCustom"),
[](Resource* p) {
cout << " Custom shared_ptr deleter!" << endl;
delete p;
}
);
ptr->use();
cout << "Scope ending..." << endl;
}
cout << "\n💡 Useful for: file handles, C APIs, etc." << endl;
cout << endl;
// ========================================================
// PART 9: SMART POINTERS IN CONTAINERS
// ========================================================
cout << "--- PART 9: SMART POINTERS IN CONTAINERS ---" << endl << endl;
cout << "Creating vector of shared_ptr:" << endl;
{
vector<shared_ptr<Resource>> resources;
resources.push_back(make_shared<Resource>("Vec1"));
resources.push_back(make_shared<Resource>("Vec2"));
resources.push_back(make_shared<Resource>("Vec3"));
cout << "\nUsing all resources:" << endl;
for (const auto& r : resources) {
r->use();
}
cout << "\nRemoving first element:" << endl;
resources.erase(resources.begin());
cout << "\nRemaining count: " << resources.size() << endl;
cout << "\nClearing vector:" << endl;
resources.clear();
}
cout << endl;
// ========================================================
// PART 10: SUMMARY AND GUIDELINES
// ========================================================
cout << "--- PART 10: SUMMARY ---" << endl << endl;
cout << "When to use each smart pointer:" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "unique_ptr:" << endl;
cout << " • Exclusive ownership" << endl;
cout << " • Factory functions returning new objects" << endl;
cout << " • Members of classes (PIMPL idiom)" << endl;
cout << endl;
cout << "shared_ptr:" << endl;
cout << " • Shared ownership needed" << endl;
cout << " • Objects in multiple data structures" << endl;
cout << " • When ownership is truly shared" << endl;
cout << endl;
cout << "weak_ptr:" << endl;
cout << " • Breaking circular references" << endl;
cout << " • Caching (check if object still exists)" << endl;
cout << " • Observer pattern" << endl;
cout << "\nGuidelines:" << endl;
cout << "─────────────────────────────────────────" << endl;
cout << "• Use unique_ptr by default" << endl;
cout << "• Use make_unique/make_shared" << endl;
cout << "• Pass by reference, not by smart pointer" << endl;
cout << "• Pass unique_ptr to transfer ownership" << endl;
cout << "• Return smart pointers from factories" << endl;
cout << endl;
cout << "============================================" << endl;
cout << "SMART POINTERS SUMMARY:" << endl;
cout << "============================================" << endl;
cout << "• unique_ptr: single owner, zero overhead" << endl;
cout << "• shared_ptr: multiple owners, ref counted" << endl;
cout << "• weak_ptr: observer, breaks cycles" << endl;
cout << "• Use make_unique/make_shared" << endl;
cout << "• Prefer unique_ptr by default" << endl;
cout << "============================================" << endl;
return 0;
}
// ============================================================
// EXERCISES:
// ============================================================
/*
* 1. Create a simple linked list using shared_ptr for next
* and weak_ptr for prev (doubly linked, no circular leak)
*
* 2. Implement a simple cache class using weak_ptr:
* - Store weak_ptrs to objects
* - Return shared_ptr if object exists, null otherwise
*
* 3. Create a factory function that returns unique_ptr to
* derived classes based on a parameter
*
* 4. Implement the PIMPL idiom using unique_ptr
*
* 5. Create a custom deleter for a file handle (FILE*)
* that calls fclose()
*/