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 <iostream>
#include <memory>
class Child; // Forward declaration
class Parent {
public:
std::shared_ptr<Child> childPtr; // Shared ownership of Child
~Parent() {
std::cout << "Parent destroyed\n";
}
};
class Child {
public:
std::shared_ptr<Parent> parentPtr; // Shared ownership of Parent
~Child() {
std::cout << "Child destroyed\n";
}
};
int main() {
// Create shared_ptr instances
std::shared_ptr<Parent> parent = std::make_shared<Parent>();
std::shared_ptr<Child> child = std::make_shared<Child>();
// Establish mutual (cyclic) ownership
parent->childPtr = child; // Parent owns Child
child->parentPtr = parent; // Child owns Parent
// ❗ Both shared_ptrs now keep each other alive
// ❌ Memory will NOT be freed — destructors won't be called
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
Real-World Example: Breaking a Cycle
Let’s say you have two classes that hold shared references to each other:
#include <iostream>
#include <memory>
class Child; // Forward declaration
class Parent {
public:
std::shared_ptr<Child> childPtr; // Shared ownership of Child
~Parent() {
std::cout << "Parent destroyed\n";
}
};
class Child {
public:
std::shared_ptr<Parent> parentPtr; // Shared ownership of Parent
~Child() {
std::cout << "Child destroyed\n";
}
};
int main() {
// Create shared_ptr instances
std::shared_ptr<Parent> parent = std::make_shared<Parent>();
std::shared_ptr<Child> child = std::make_shared<Child>();
// Establish mutual (cyclic) ownership
parent->childPtr = child; // Parent owns Child
child->parentPtr = parent; // Child owns Parent
// ❗ Both shared_ptrs now keep each other alive
// ❌ Memory will NOT be freed — destructors won't be called
return 0;
}
Output of this Program:
(no output)
Notice that neither Parent
nor Child
is destroyed, even though main()
ends. That’s because their shared_ptr
s are keeping each other alive:
parent
ownschild
child
ownsparent
Reference count never hits zero
Breaking Cycle Dependencies with std::weak_ptr
#include <iostream>
#include <memory>
class Child; // Forward declaration
class Parent {
public:
std::weak_ptr<Child> childPtr; // Non-owning reference to Child
~Parent() {
std::cout << "Parent destroyed\n";
}
};
class Child {
public:
std::weak_ptr<Parent> parentPtr; // Non-owning reference to Parent
~Child() {
std::cout << "Child destroyed\n";
}
};
int main() {
// Create shared ownership for both Parent and Child
std::shared_ptr<Parent> parent = std::make_shared<Parent>();
std::shared_ptr<Child> child = std::make_shared<Child>();
// Establish mutual references using weak_ptr
parent->childPtr = child; // Parent observes (but does not own) Child
child->parentPtr = parent; // Child observes (but does not own) Parent
// Exiting main will destroy both parent and child
// No memory leak occurs because weak_ptr does not increase the reference count
return 0;
}
Child destroyed
Parent destroyed
Both
Parent
andChild
usestd::weak_ptr
for non-owning references to avoid increasing reference counts.When
main()
ends, bothshared_ptr
s (parent
andchild
) go out of scope.The
Parent
andChild
objects are properly destroyed, and no memory leaks occur.Destructors print confirmation that both objects were cleaned up.
Leave a comment
Your email address will not be published. Required fields are marked *