What is a C++ weak pointer and where is it used? smart pointers part III

Introduction

A weak pointer is a smart pointer that does not take ownership of an object but act as an observer. In other words, it does not participate in reference counting to delete an object or extend its lifetime. Weak pointers are mainly used to break the circular dependency that shared pointers create.

Prerequisites

Here, I imagine you have some knowledge of raw pointers, unique pointers, shared pointers, and auto keyword.

My compiler is GCC 10.2 with the flag -std=c++20.

To enhance readability, in some examples headers and the main function are dropped:

#include <iostream> // For std::cout
#include <memory> // For std::weak_ptr, std::shared_ptr, std::make_shared

using namespace std; // dropping std::

// class definitions

int main(){

    // implementations

    return 0;
}

Definition

Consider the example class below

struct Person{
    string Name;
    Person(string n):Name(n){}
};

A weak pointer working with an object of Person is defined as

std::weak_ptr<Person> wp;

A weak pointer is used to observe the object of a shared pointer

auto teacher = make_shared<Person>("Jack");

wp = teacher; // wp watches the managed object of teacher
(.Get 1)

Somewhere else that we are not sure if the object is still there or not:

if (auto tempSharedPointer = wp.lock()){ // if Jack there
    cout<< tempSharedPointer-> Name;
} else
{
    cout<< "The object is not there.";
}

In the above example lock() returns a temporary shared pointer pointing to the managed object, Jack.

Under the hood

Let’s work out weak pointers with an example:

struct Person{
    string Name;
    Person(string n):Name(n){}
};

int main(){

    // initial state
    auto teacher = make_shared<Person>("Jack");
    auto coach = teacher;
    weak_ptr<Person> wp = teacher;

    if (auto temp = wp.lock())
        cout<< temp-> Name; //  Jack
    
    // coach is reset
    coach.reset();

    // teacher is reset to Rose
    teacher.reset(new Person("Rose"));

    if (wp.expired()) // true
        cout<< "The old teacher is not there."; 
}

An object of shared pointers has a control block, which counts the number of weak and shared pointers. When the shared counter reaches zero the object is deleted, but the control block is alive until the weak counter reaches zero as well. The code can be sketched as the image below

(.Get 1)

Why we need weak pointers?

In previous examples, I showed how to use a weak pointer but we don’t need them there. They can be replaced by shared pointers. The main reason weak pointers are invented is to break circular dependency of shared pointers. Otherwise, they cannot delete their objects:

struct Person;

struct Team{
    shared_ptr<Person> goalKeeper;
    ~Team(){cout<<"Team destructed.";}
};
struct Person{
    shared_ptr<Team> team;
    ~Person(){cout<<"Person destructed.";}
};

int main(){
    
    
    auto Barca = make_shared<Team>();
    auto Valdes = make_shared<Person>();
    
    Barca->goalKeeper = Valdes;
    Valdes->team = Barca;
    
    return 0;

}

In the example above, the destructors are not called and we have a memory leak. The managed object of a shared pointer is deleted when the reference count reaches zero. If Barca goes out of scope, it is not deleted since the managed object is still pointed by Valdes.team. When Valdes goes out of scope, its managed object is not deleted either as it is pointed by Barca.goalKeeper.

This case can be solved with a weak pointer:

struct Person;

struct Team{
    shared_ptr<Person> goalKeeper;
    ~Team(){cout<<"Team destructed.";}
};
struct Person{
    weak_ptr<Team> team; // This line is changed.
    ~Person(){cout<<"Person destructed.";}
};

int main(){
    
    
    auto Barca = make_shared<Team>();
    auto Valdes = make_shared<Person>();
    
    Barca->goalKeeper = Valdes;
    Valdes->team = Barca;
    
    return 0;

}

Both destructors are called. When Barca goes out of scope, it will be destructed as it is pointed by a weak pointer (non-owner). Valdes is destructed easily as it is not pointed by anything.

One may say what if Valdes goes out of scope first? its object is not deleted but its reference count changes to 1. When Barca goes out of scope, it destructs its managed object which destructs the goalKeeper i.e. Valdes.

More

In case you have missed them, I have similar style posts on

If you are interested in various ways to create arrays and vectors in C++, see this post.

References

cppreference

Tags ➡ C++ Pointers

Subscribe

I notify you of my new posts

Latest Posts

Comments

2 comments
Mike 8-Mar-2022
Just finished reading through your various articles about weak, shared, unique, and raw pointers. Thank you for writing this out, it helped demystify the topics for me, and I learned a few new things that will definitely help me write better code.
Dawe 4-Apr-2022
Thank you for your clarity.