Shared and Weak Pointers: Subtle Connections
Due to the reference counting mechanism, shared pointers are prone to forming reference circles which can lead to memory leaks. To address this problem, shared pointers have a counterpart - weak pointers.
Let's consider an implementation of the listener pattern. There is an 𝙴𝚟𝚎𝚗𝚝𝚂𝚘𝚞𝚛𝚌𝚎 class that produces events and a 𝙻𝚒𝚜𝚝𝚎𝚗𝚎𝚛 class that consumes those events. When an event is raised, the listener gets notified through the 𝙻𝚒𝚜𝚝𝚎𝚗𝚎𝚛::𝚗𝚘𝚝𝚒𝚏𝚢() method and should pull updates from 𝙴𝚟𝚎𝚗𝚝𝚂𝚘𝚞𝚛𝚌𝚎 using the 𝙴𝚟𝚎𝚗𝚝𝚂𝚘𝚞𝚛𝚌𝚎::𝚐𝚎𝚝𝚄𝚙𝚍𝚊𝚝𝚎𝚜() method.
The dependency between these two classes can be represented in the following diagram:
In this setup, the objects form a typical reference circle. If only shared pointers are used, the memory cannot be deallocated, even when there are no external references. To prevent this, 𝙴𝚟𝚎𝚗𝚝𝚂𝚘𝚞𝚛𝚌𝚎 holds a shared pointer to listeners, while 𝙻𝚒𝚜𝚝𝚎𝚗𝚎𝚛 holds only a weak pointer to 𝙴𝚟𝚎𝚗𝚝𝚂𝚘𝚞𝚛𝚌𝚎.
class EventSource {
public:
std::vector<std::string> getUpdates() const;
private:
std::vector<std::shared_ptr<Listener>> m_listeners;
};
class Listener {
public:
Listener(std::weak_ptr<EventSource> source);
virtual void notify() = 0;
protected:
std::weak_ptr<EventSource> getSource();
private:
std::weak_ptr<EventSource> m_source;
};
Creating a new copy of 𝚜𝚝𝚍::𝚠𝚎𝚊𝚔_𝚙𝚝𝚛 does not increase the reference count. When you need actual access to the object, a 𝚜𝚝𝚍::𝚜𝚑𝚊𝚛𝚎𝚍_𝚙𝚝𝚛 should be obtained, as shown in a possible implementation of 𝙻𝚒𝚜𝚝𝚎𝚗𝚎𝚛::𝚗𝚘𝚝𝚒𝚏𝚢():
void ConcreteListener::notify() {
std::shared_ptr<EventSource> source = getSource().lock();
if (!source) {
return;
}
auto updates = source->getUpdates();
// ...
}
To obtain a shared pointer from a weak pointer, follow these steps: