C++ Shared Pointer
In modern C++, managing dynamic memory manually using raw pointers can lead to memory leaks, dangling pointers, and undefined behavior. To address these issues, C++ introduced smart pointers, which automatically manage memory. One of the most commonly used smart pointers is the std::shared_ptr.
A shared_ptr is a smart pointer that maintains reference counting. Multiple shared_ptr instances can point to the same object. The object is automatically deleted when the last shared_ptr pointing to it is destroyed or reset.
1. Basic Concepts
std::shared_ptr is included in the <memory> header. Unlike raw pointers, shared_ptr keeps track of the number of references to the dynamically allocated object using a reference count. When the reference count drops to zero, the memory is automatically freed.
Key points about shared_ptr:
1. Multiple shared_ptr can share ownership of the same object.
2. Memory is automatically released when the last owner is gone.
3. Avoids manual calls to delete.
4. Can be used in standard containers like vector, list, map, etc.
2. Creating Shared Pointers
There are multiple ways to create a shared pointer in C++.
2.1 Using std::make_shared (Recommended)
std::make_shared is the preferred way because it is safer and more efficient.
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr = std::make_shared<int>(100);
std::cout << "Value: " << *ptr << std::endl;
std::cout << "Reference Count: " << ptr.use_count() << std::endl;
return 0;
}
Value: 100 Reference Count: 1
2.2 Using Constructor
You can also create a shared_ptr by directly calling its constructor with a raw pointer. However, this is less safe.
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> ptr(new int(200));
std::cout << "Value: " << *ptr << std::endl;
std::cout << "Reference Count: " << ptr.use_count() << std::endl;
return 0;
}
Value: 200 Reference Count: 1
3. Shared Pointer Operations
Shared pointers support various operations:
1. Dereference using * and -> operators.
2. Checking reference count using use_count().
3. Resetting the pointer using reset() to release ownership.
4. Swapping pointers using swap().
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> p1 = std::make_shared<int>(50);
std::shared_ptr<int> p2 = p1; // Shared ownership
std::cout << "Value of p1: " << *p1 << std::endl;
std::cout << "Reference Count: " << p1.use_count() << std::endl;
p1.reset(); // Release ownership from p1
std::cout << "After p1.reset(), Reference Count of p2: " << p2.use_count() << std::endl;
return 0;
}
Value of p1: 50 Reference Count: 2 After p1.reset(), Reference Count of p2: 1
4. Shared Pointers in Containers
Shared pointers can be stored in standard containers such as vector, map, and list. This helps manage memory automatically for dynamic objects stored in containers.
#include <iostream>
#include <vector>
#include <memory>
int main() {
std::vector<std::shared_ptr<int>> vec;
vec.push_back(std::make_shared<int>(10));
vec.push_back(std::make_shared<int>(20));
vec.push_back(std::make_shared<int>(30));
for(auto &ptr : vec) {
std::cout << "Value: " << *ptr << ", Ref Count: " << ptr.use_count() << std::endl;
}
return 0;
}
Value: 10, Ref Count: 1 Value: 20, Ref Count: 1 Value: 30, Ref Count: 1
5. Common Mistakes with Shared Pointers
1. Creating a shared pointer from a raw pointer that is already managed → leads to double deletion.
2. Cyclic references between shared_ptr objects → memory leak because reference count never reaches zero.
3. Mixing shared_ptr and raw delete → unsafe, may cause undefined behavior.
4. Passing shared_ptr by value unnecessarily → can increase reference count overhead, prefer passing by const reference when possible.
6. Weak Pointers to Avoid Cyclic References
A std::weak_ptr is a smart pointer that does not contribute to reference count. It is used to break cyclic dependencies between shared pointers. The weak pointer can be converted to shared_ptr temporarily using lock().
#include <iostream>
#include <memory>
struct Node {
int data;
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev;
Node(int val) : data(val) {}
};
int main() {
auto first = std::make_shared<Node>(1);
auto second = std::make_shared<Node>(2);
first->next = second;
second->prev = first; // weak_ptr avoids cyclic reference
std::cout << "First Node Data: " << first->data << std::endl;
std::cout << "Second Node Data: " << second->data << std::endl;
return 0;
}
7. Best Practices
1. Prefer std::make_shared over constructor with new for efficiency and safety.
2. Avoid raw pointers mixed with shared_ptr.
3. Pass shared_ptr by const reference when passing to functions to avoid unnecessary reference count increase.
4. Use weak_ptr to prevent memory leaks from cyclic references.
5. Understand ownership semantics before using shared_ptr extensively.
Conclusion
C++ shared pointers provide automatic memory management through reference counting. They simplify code, prevent memory leaks, and integrate well with standard containers. By following best practices and understanding potential pitfalls like cyclic references, shared_ptr becomes a powerful tool in modern C++ programming.
Codecrown