Definition
The definition of std::weak_ptr can be summarized as follows:
- std::weak_ptr is a smart pointer that provides non-owning, weak references to objects managed by std::shared_ptr.
- It allows observing an object without affecting its lifetime, preventing strong reference cycles and memory leaks.
- It can be converted to a std::shared_ptr to access the managed object if it's still valid.
Understanding std::weak_ptr
std::weak_ptr is a smart pointer introduced in C++11 as part of the standard library <memory> header. It complements std::shared_ptr by providing a non-owning, weak reference to an object managed by std::shared_ptr. Unlike std::shared_ptr, std::weak_ptr does not contribute to the reference count of the managed object. Instead, it allows observing the object without extending its lifetime. If the object is deleted, the std::weak_ptr automatically becomes empty, preventing access to the invalid memory.
Need of weak_ptr
The development of std::weak_ptr addresses the issue of reference cycles and potential memory leaks that may arise when using std::shared_ptr. Reference cycles occur when objects have circular dependencies, causing each object to hold a strong reference to the other, preventing them from being deallocated. By introducing std::weak_ptr, developers can break these cycles and avoid memory leaks while still being able to observe the objects when necessary.
Consider a scenario where you have two classes, Parent
and Child
, with a parent-child relationship managed using std::shared_ptr
. Each Parent
object holds a std::shared_ptr
to its child, and each Child
object holds a std::shared_ptr
to its parent. This creates a circular reference between the parent and child objects.
Without std::weak_ptr
:
#include <memory>
class Child;
class Parent {
public:
std::shared_ptr<Child> childPtr;
};
class Child {
public:
std::shared_ptr<Parent> parentPtr;
};
int main() {
std::shared_ptr<Parent> parent = std::make_shared<Parent>();
std::shared_ptr<Child> child = std::make_shared<Child>();
// Establishing circular reference
parent->childPtr = child;
child->parentPtr = parent;
// Memory leak due to circular reference
return 0;
}
In this example, parent
and child
objects form a circular reference through std::shared_ptr
, resulting in a memory leak. Even after all external references to the parent and child objects are gone, they will not be deallocated because each holds a strong reference to the other.
Using std::weak_ptr
to Break Circular References:
#include <memory>
class Child;
class Parent {
public:
std::weak_ptr<Child> childPtr; // Using weak_ptr instead of shared_ptr
};
class Child {
public:
std::weak_ptr<Parent> parentPtr; // Using weak_ptr instead of shared_ptr
};
int main() {
std::shared_ptr<Parent> parent = std::make_shared<Parent>();
std::shared_ptr<Child> child = std::make_shared<Child>();
// Establishing circular reference
parent->childPtr = child;
child->parentPtr = parent;
// No memory leak, as weak_ptr does not contribute to reference count
return 0;
}
In this modified example, we have replaced std::shared_ptr
with std::weak_ptr
for the parent-child relationship. By using std::weak_ptr
, we break the strong reference cycle, ensuring that the parent and child objects can be deallocated when no external references exist.
Creating a Weak Pointer
You can create a std::weak_ptr from a std::shared_ptr or directly from a raw pointer.
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
// or
std::weak_ptr<int> weakPtr = std::make_shared<int>(42);
Function
1 expired() Function (Checking Validity):
- The expired() function checks whether the referenced object has been deleted (expired) or is still valid.
- It returns true if the referenced object has been deleted, and false otherwise.
Example:
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
// Check if the referenced object is still valid
if (!weakPtr.expired()) {
std::cout << "Referenced object is still valid." << std::endl;
} else {
std::cout << "Referenced object has been deleted." << std::endl;
}
2 lock() Function:
- The lock() function converts the std::weak_ptr to a std::shared_ptr, allowing access to the referenced object if it's still valid.
- It returns an empty std::shared_ptr if the referenced object has been deleted.
Example:
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
// Attempt to convert weak_ptr to shared_ptr
std::shared_ptr<int> sharedPtrCopy = weakPtr.lock();
if (sharedPtrCopy) {
std::cout << "Referenced object value: " << *sharedPtrCopy << std::endl;
} else {
std::cout << "Referenced object has been deleted." << std::endl;
}
3 use_count() Function:
- The use_count() function returns the number of std::shared_ptr instances sharing ownership of the referenced object.
- It's important to note that use_count() is not part of std::weak_ptr itself but is accessed through a std::shared_ptr obtained using the lock() function.
Example:
std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
std::weak_ptr<int> weakPtr = sharedPtr;
// Obtain a shared_ptr from weak_ptr
std::shared_ptr<int> sharedPtrCopy = weakPtr.lock();
// Access use_count through the shared_ptr
if (sharedPtrCopy) {
std::cout << "Number of shared_ptr instances: " << sharedPtrCopy.use_count() << std::endl;
}
4 Reset:
- The
reset()
function resets thestd::weak_ptr
, releasing its reference of the objects.
Example:
weakPtr.reset(); // Releases the reference