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
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
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
Latest Posts
- A C++ MPI code for 2D unstructured halo exchange
- Essential bash customizations: prompt, ls, aliases, and history date
- Script to copy a directory path in memory in Bash terminal
- What is the difference between .bashrc, .bash_profile, and .profile?
- A C++ MPI code for 2D arbitrary-thickness halo exchange with sparse blocks