A smart pointer is a stack-allocated object that wraps a raw pointer and automatically manages the lifetime of dynamically allocated memory.Smart pointers are provided by the C++ Standard Library in the <memory> header and help prevent:
- Memory leaks
- Dangling pointers
- Double deletion
Types of Smart Pointers
- std::auto_ptr (deprecated)
- std::unique_ptr
- std::shared_ptr
- std::weak_ptr
They all are declared in a memory header file( #include<memory>) which is used to manage dynamic memory allocation.
1. auto_ptr (Deprecated)
auto_ptr was the first smart pointer introduced in C++. It manages a dynamically allocated object and deletes it when the auto_ptr goes out of scope.
Key Characteristics
- Exclusive ownership
- Ownership is transferred on copy
- Source pointer becomes NULL
- Unsafe copy semantics
Note: Deprecated in C++11 and removed in C++17
Example:
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
void show() { cout << "A::show()" << endl; }
};
int main() {
auto_ptr<A> p1(new A);
p1->show();
cout << p1.get() << endl;
auto_ptr<A> p2(p1); // ownership transferred
p2->show();
cout << p1.get() << endl; // p1 is now empty
cout << p2.get() << endl;
return 0;
}
Output:
A::show()
0x1b42c20
A::show()
0
0x1b42c20Explanation:
- Copying p1 to p2 moves ownership, leaving p1 null.
- This behavior breaks STL container requirements.
Why auto_ptr is Deprecated
- Violates normal copy semantics
- Cannot be used safely with STL containers
- Leads to subtle bugs due to silent ownership transfer
2. unique_ptr
unique_ptr was introduced in C++11 as a safe replacement for auto_ptr.
Key Characteristics
- Exclusive ownership
- Cannot be copied
- Ownership can be transferred using std::move()
- Lightweight and efficient
- Supports custom deleters and arrays
Invalid Copy (Compile-Time Error):
unique_ptr<A> p1(new A);
unique_ptr<A> p2 = p1; // ERROR
But, unique_ptr can be moved using the new move semantics i.e. using the std::move() function to transfer ownership of the contained pointer to another unique_ptr.
unique_ptr<A> p2 = move(p1); // Ownership transferred
So, it’s best to use unique_ptr when we want a single pointer to an object that will be reclaimed when that single pointer is destroyed.
Example:
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
void show() { cout << "A::show()" << endl; }
};
int main() {
unique_ptr<A> p1(new A);
p1->show();
cout << p1.get() << endl;
unique_ptr<A> p2 = move(p1);
p2->show();
cout << p1.get() << endl;
cout << p2.get() << endl;
return 0;
}
Output
A::show() 0x332feb0 A::show() 0 0x332feb0
When to Use unique_ptr
- When only one owner should exist
- Default choice for resource management
- Best replacement for raw pointers
3. shared_ptr
shared_ptr implements shared ownership using reference counting.
Key Characteristics:
- Multiple pointers can own the same object
- Object is destroyed when reference count becomes zero
- Slight overhead due to reference counting
Reference Counting:
- use_count() returns number of active owners
- Incremented on copy
- Decremented on destruction/reset
Example:
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
void show() { cout << "A::show()" << endl; }
};
int main() {
shared_ptr<A> p1(new A);
p1->show();
shared_ptr<A> p2 = p1;
p2->show();
cout << p1.use_count() << endl;
cout << p2.use_count() << endl;
p1.reset();
cout << p2.use_count() << endl;
return 0;
}
Output
A::show() A::show() 2 2 1
When to Use shared_ptr
- When multiple owners must share a resource
- Ownership lifetime is not clearly hierarchical
4. weak_ptr
weak_ptr is a non-owning reference to an object managed by shared_ptr.
Key Characteristics
- Does not increase reference count
- Prevents circular dependency
- Must be converted using lock() before use
Problem: Circular Dependency with shared_ptr
If two objects hold shared_ptr to each other:
- Reference count never reaches zero
- Memory leak occurs
This is the reason we use weak pointers(weak_ptr) as they are not reference counted. So, the class in which weak_ptr is declared doesn’t have a stronghold over it i.e. the ownership isn’t shared, but they can have access to these objects.
So, in the case of shared_ptr because of cyclic dependency use_count never reaches zero which is prevented by using weak_ptr, which removes this problem by declaring A_ptr as weak_ptr, thus class A does not own it, only has access to it and we also need to check the validity of object as it may go out of scope. In general, it is a design issue.
Example:
#include <iostream>
#include <memory>
using namespace std;
class A {
public:
void show() { cout << "A::show()" << endl; }
};
int main() {
shared_ptr<A> p1(new A);
weak_ptr<A> wp = p1;
cout << wp.use_count() << endl; // does not increment count
if (auto sp = wp.lock()) {
sp->show();
}
return 0;
}
Output
1 A::show()
When to Use weak_ptr
- To break cyclic references
- When you want access without ownership
- Observer-like relationships
