Smart pointers are like responsible pet owners - they automatically take care of their resources, feeding them when needed and cleaning up after them. No more memory leaks or dangling pointers!
unique_ptr is like having a single key to a house - only one person can own it at a time. When they're done, the house is automatically sold (memory freed)!
#include
#include
using namespace std;
// Basic unique_ptr usage
void uniquePtrBasics() {
// Creating unique_ptr
unique_ptr p1(new int(42)); // Direct initialization
auto p2 = make_unique(42); // Preferred: make_unique
// Accessing the value
cout << *p2 << endl; // Dereference: 42
cout << p2.get() << endl; // Get raw pointer
// unique_ptr with arrays
auto arr = make_unique(10); // Array of 10 ints
arr[0] = 100;
// Custom deleter
auto fileDeleter = [](FILE* f) {
if (f) fclose(f);
};
unique_ptr file(
fopen("data.txt", "r"), fileDeleter
);
}
// Ownership transfer
unique_ptr createMessage() {
auto msg = make_unique("Hello from function!");
return msg; // Automatic move
}
void transferOwnership() {
auto p1 = make_unique(42);
// unique_ptr p2 = p1; // ERROR: Can't copy
unique_ptr p2 = move(p1); // OK: Move ownership
if (!p1) {
cout << "p1 is now null" << endl;
}
// Reset and release
p2.reset(); // Delete object, set to null
p2.reset(new int(100)); // Delete old, own new
int* raw = p2.release(); // Release ownership
delete raw; // Now we must delete manually
}
// Using unique_ptr in classes
class Resource {
private:
unique_ptr data;
size_t size;
public:
Resource(size_t n) : data(make_unique(n)), size(n) {
// Initialize array
for (size_t i = 0; i < size; ++i) {
data[i] = i;
}
}
// No need for destructor - unique_ptr handles it!
// No need for copy constructor/assignment - deleted by default
// Move constructor (compiler-generated is fine)
Resource(Resource&&) = default;
Resource& operator=(Resource&&) = default;
int& operator[](size_t i) { return data[i]; }
};
shared_ptr is like a shared apartment - multiple roommates can have keys. The apartment is only sold when the last roommate moves out!
// Basic shared_ptr usage
void sharedPtrBasics() {
// Creating shared_ptr
shared_ptr sp1(new int(42)); // Direct
auto sp2 = make_shared(42); // Preferred: more efficient
// Copying increases reference count
shared_ptr sp3 = sp2; // Count: 2
{
shared_ptr sp4 = sp2; // Count: 3
} // Count: 2 (sp4 destroyed)
// Check reference count
cout << "Use count: " << sp2.use_count() << endl;
// Check if unique owner
if (sp1.unique()) {
cout << "sp1 is the only owner" << endl;
}
}
// Circular reference problem
class Node {
public:
int value;
shared_ptr next;
shared_ptr prev; // Creates cycle!
Node(int val) : value(val) {
cout << "Node " << value << " created" << endl;
}
~Node() {
cout << "Node " << value << " destroyed" << endl;
}
};
void circularReferenceProblem() {
auto node1 = make_shared(1);
auto node2 = make_shared(2);
node1->next = node2;
node2->prev = node1; // Circular reference!
// When function ends, nodes are NOT destroyed
// Reference count never reaches 0
}
// Solution: Use weak_ptr
class SafeNode {
public:
int value;
shared_ptr next;
weak_ptr prev; // Weak reference breaks cycle
SafeNode(int val) : value(val) {}
};
// Custom deleter with shared_ptr
void customDeleterExample() {
auto arrayDeleter = [](int* p) {
cout << "Deleting array" << endl;
delete[] p;
};
shared_ptr sp(new int[10], arrayDeleter);
// Or use default_delete for arrays
shared_ptr arr(new int[10]);
}
// Aliasing constructor
struct Person {
string name;
int age;
};
void aliasingExample() {
auto person = make_shared(Person{"Alice", 30});
// Create shared_ptr to member
shared_ptr namePtr(person, &person->name);
// namePtr keeps person alive even if person goes out of scope
}
weak_ptr is like having a business card - you can call the number to check if the business still exists, but the card itself doesn't keep the business running!
// Basic weak_ptr usage
void weakPtrBasics() {
shared_ptr sp = make_shared(42);
weak_ptr wp = sp; // Create weak_ptr
// Check if still valid
if (!wp.expired()) {
// Convert to shared_ptr to use
if (auto locked = wp.lock()) {
cout << "Value: " << *locked << endl;
// locked keeps object alive in this scope
}
}
sp.reset(); // Destroy object
if (wp.expired()) {
cout << "Object has been destroyed" << endl;
}
}
// Observer pattern with weak_ptr
class Subject;
class Observer {
private:
weak_ptr subject;
public:
void observe(shared_ptr s) {
subject = s;
}
void update() {
if (auto s = subject.lock()) {
// Subject still exists, can safely use
cout << "Updating based on subject" << endl;
} else {
cout << "Subject has been destroyed" << endl;
}
}
};
// Tree structure with parent pointers
class TreeNode : public enable_shared_from_this {
private:
int value;
weak_ptr parent; // Weak to avoid cycle
vector> children; // Strong ownership
public:
TreeNode(int val) : value(val) {}
void addChild(shared_ptr child) {
children.push_back(child);
child->parent = shared_from_this(); // Get shared_ptr to this
}
shared_ptr getParent() {
return parent.lock(); // May return nullptr
}
};
// Cache with automatic cleanup
template
class Cache {
private:
map> cache;
public:
void insert(const Key& key, shared_ptr value) {
cache[key] = value;
}
shared_ptr get(const Key& key) {
auto it = cache.find(key);
if (it != cache.end()) {
if (auto value = it->second.lock()) {
return value; // Still valid
} else {
cache.erase(it); // Clean up expired entry
}
}
return nullptr;
}
void cleanup() {
for (auto it = cache.begin(); it != cache.end(); ) {
if (it->second.expired()) {
it = cache.erase(it);
} else {
++it;
}
}
}
};
// Performance comparison
class PerformanceDemo {
public:
// unique_ptr: Zero overhead
void uniquePtrPerf() {
unique_ptr up(new int(42));
// Same performance as raw pointer
// No reference counting
// Inline destructor
}
// shared_ptr: Reference counting overhead
void sharedPtrPerf() {
shared_ptr sp(new int(42));
// Atomic reference counting (thread-safe)
// Control block allocation
// Virtual destructor call
}
// make_shared optimization
void makeSharedOptimization() {
// Two allocations: object + control block
shared_ptr sp1(new int(42));
// Single allocation: object + control block together
auto sp2 = make_shared(42); // More cache-friendly
}
// Moving vs copying
void moveVsCopy() {
auto sp1 = make_shared();
// Copying: Atomic increment/decrement
shared_ptr sp2 = sp1; // Slower
// Moving: No atomic operations
shared_ptr sp3 = move(sp1); // Faster
}
};
// Custom allocators
template
class PoolAllocator {
// Custom allocation strategy
};
void customAllocatorExample() {
// With custom allocator
allocator alloc;
shared_ptr sp(
allocate_shared(alloc, 42)
);
}
Create a system that manages various resources using smart pointers:
class FileHandle {
private:
FILE* file;
public:
FileHandle(const string& filename, const string& mode);
~FileHandle();
// TODO: Make it work with unique_ptr
};
class ThreadPool {
private:
vector> workers;
queue> tasks;
public:
void enqueue(function task);
// TODO: Implement with proper synchronization
};
template
class ObjectPool {
private:
stack> available;
vector> inUse;
public:
shared_ptr acquire();
void release(shared_ptr obj);
// TODO: Implement recycling mechanism
};
// Polymorphic container with unique_ptr
class Shape {
public:
virtual ~Shape() = default;
virtual double area() const = 0;
virtual void draw() const = 0;
};
class Circle : public Shape {
double radius;
public:
Circle(double r) : radius(r) {}
double area() const override { return 3.14159 * radius * radius; }
void draw() const override { cout << "Drawing circle" << endl; }
};
void polymorphicContainer() {
vector> shapes;
// Add different shapes
shapes.push_back(make_unique(5.0));
shapes.push_back(make_unique(4.0, 6.0));
// Can't copy the vector, but can move it
vector> newShapes = move(shapes);
// Process all shapes polymorphically
for (const auto& shape : newShapes) {
shape->draw();
cout << "Area: " << shape->area() << endl;
}
}
// Shared ownership in containers
class Task {
string name;
function work;
public:
Task(string n, function w) : name(n), work(w) {}
void execute() { work(); }
};
class TaskManager {
vector> pendingTasks;
vector> runningTasks;
public:
void addTask(shared_ptr task) {
pendingTasks.push_back(task);
}
void startTask(size_t index) {
if (index < pendingTasks.size()) {
// Task can be in both vectors
runningTasks.push_back(pendingTasks[index]);
}
}
};
Implement a thread-safe memory pool using smart pointers: