C++ Weak Pointer

In modern C++, memory management is greatly simplified using smart pointers such as std::shared_ptr and std::unique_ptr. However, shared pointers can sometimes cause memory leaks due to cyclic references. A std::weak_ptr is a smart pointer that provides a solution to this problem by holding a non-owning reference to an object managed by a shared pointer.

A weak pointer does not increase the reference count, and it can be used to observe or access an object temporarily without affecting its lifetime.

1. Basic Concepts of Weak Pointer

Key points about std::weak_ptr:

1. Weak pointers do not own the object and do not affect reference count.

2. They prevent cyclic references that could prevent memory deallocation.

3. To access the object, weak_ptr must be converted to a shared_ptr using lock().

4. They are included in the <memory> header and work closely with shared_ptr.

2. Creating Weak Pointers

A weak pointer is created from an existing shared pointer. It does not take ownership of the object.

C++
Creating a weak pointer
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(42);
    std::weak_ptr<int> weakPtr(sharedPtr);

    std::cout << "Reference Count of sharedPtr: " << sharedPtr.use_count() << std::endl;

    if(auto temp = weakPtr.lock()) { // Convert weak_ptr to shared_ptr
        std::cout << "Value: " << *temp << std::endl;
    }

    return 0;
}
Reference Count of sharedPtr: 1
Value: 42

Notice that the weak pointer does not increase the reference count. It only observes the object.

3. Accessing Object Using Weak Pointer

Since a weak pointer does not own the object, you cannot dereference it directly. To safely access the object, you need to convert it to a shared_ptr using lock().

Example:

C++
Accessing weak_ptr object safely
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sharedPtr = std::make_shared<int>(100);
    std::weak_ptr<int> weakPtr(sharedPtr);

    std::shared_ptr<int> tempPtr = weakPtr.lock();
    if(tempPtr) {
        std::cout << "Value through weak_ptr: " << *tempPtr << std::endl;
    } else {
        std::cout << "Object no longer exists." << std::endl;
    }

    return 0;
}
Value through weak_ptr: 100

4. Breaking Cyclic References

A common issue with shared_ptr is cyclic references. For example, two objects pointing to each other with shared_ptr will never be deleted, causing memory leaks. Weak pointers solve this problem by creating a non-owning reference.

C++
Using weak_ptr to break cycles
#include <iostream>
#include <memory>

struct Node {
    int data;
    std::shared_ptr<Node> next;
    std::weak_ptr<Node> prev; // weak_ptr breaks cyclic reference
    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 cycle

    std::cout << "First Node: " << first->data << std::endl;
    std::cout << "Second Node: " << second->data << std::endl;

    return 0;
}

5. Common Mistakes with Weak Pointers

1. Trying to dereference weak_ptr directly → leads to compilation error.

2. Assuming weak_ptr keeps the object alive → it does not. Always check using lock() or expired().

3. Forgetting to convert weak_ptr to shared_ptr before using it → unsafe access.

4. Misusing weak_ptr with unique_ptr → weak_ptr works only with shared_ptr.

6. Additional Weak Pointer Operations

1. expired() – Checks whether the object managed by the shared pointer has been deleted.

2. use_count() – Returns the number of shared_ptr instances managing the object. Does not count weak_ptr.

C++
expired() and use_count() example
#include <iostream>
#include <memory>

int main() {
    std::shared_ptr<int> sp = std::make_shared<int>(500);
    std::weak_ptr<int> wp(sp);

    std::cout << "Use count: " << wp.use_count() << std::endl;
    std::cout << "Expired? " << (wp.expired() ? "Yes" : "No") << std::endl;

    sp.reset(); // delete the object

    std::cout << "Expired after reset? " << (wp.expired() ? "Yes" : "No") << std::endl;

    return 0;
}
Use count: 1
Expired? No
Expired after reset? Yes

7. Best Practices

1. Use weak_ptr to break cyclic references when using shared_ptr.

2. Always check if weak_ptr is expired or convert it to shared_ptr using lock() before use.

3. Avoid using weak_ptr where ownership is required. It is for observation only.

4. Do not mix weak_ptr with raw pointers or unique_ptr.

Conclusion

C++ weak pointers provide a way to reference objects managed by shared pointers without affecting their lifetime. They are crucial in preventing memory leaks due to cyclic references. By using weak_ptr with shared_ptr responsibly, you can write safer and more efficient modern C++ code.