Cycles et pointeurs faibles
Motivation
Sur le code Python suivant :
class Node:
def __init__(self):
self.friend = None
def f():
A = Node()
B = Node()
A.friend = B
B.friend = A
f()
On ne libère pas la mémoire car les compteurs de référence pour A et B étaient à 2 et passe à 1.
Weak ptr
Pour éviter des boucles dans le graphe des pointeurs, on peut utiliser des weak pointers. Un weak pointer est une structure de données qui modélise un pointeur vers une donnée, ou peut-être pas, si la donnée a été supprimée.
C++
std::shared_ptr<int> p = std::make_shared<int>(5);
std::weak_ptr wp = p;
if(std::shared_ptr<int> strong_p = wp.lock()) {
//here you can use strong_p
}
else
{
//the data does not exist anymore
}
Rust
En Rust, it is std::rc::Weak, see the manual.
Python
En Python aussi, on peut créer des pointeurs faibles :
import sys
import weakref
class Person:
pass
x = Person()
y = weakref.ref(x)
print(y)
print(y())
sys.getrefcount(x)
Dans le code ci-dessus print(y) donne :
<weakref at 0x7f730681aa40; to 'Person' at 0x7f7307588970>
Et print(y()) permet d'accéder à l'objet :
<__main__.Person object at 0x7f7307588970>
sys.getrefcount(x) donne 2 (pour x et pour la référence à x dans sys.getrefcount(x)).
import sys
import weakref
class Person:
pass
x = Person()
y = weakref.ref(x)
del x
print(y)
print(y())
Ici print(y) montre que y est toujours un pointeur faible, mais il est mort :
<weakref at 0x7f73067cba90; dead>
En accédant à la donnée qui n'existe plus, y() renvoie None.
Implémentation des weak ptr
#include <iostream>
#include <utility>
template <typename T>
class SharedPtr;
template <typename T>
class WeakPtr;
// Contrôle partagé entre SharedPtr et WeakPtr
template <typename T>
struct ControlBlock {
T* ptr;
size_t strong_count;
size_t weak_count;
ControlBlock(T* p) : ptr(p), strong_count(1), weak_count(0) {}
};
// -------- SharedPtr --------
template <typename T>
class SharedPtr {
private:
T* ptr;
ControlBlock<T>* control;
friend class WeakPtr<T>;
public:
// Constructeur
explicit SharedPtr(T* p = nullptr)
: ptr(p), control(p ? new ControlBlock<T>(p) : nullptr) {}
// Destructeur
~SharedPtr() {
release();
}
// Copie (augmente strong_count)
SharedPtr(const SharedPtr& other)
: ptr(other.ptr), control(other.control) {
if (control) control->strong_count++;
}
// Move
SharedPtr(SharedPtr&& other) noexcept
: ptr(other.ptr), control(other.control) {
other.ptr = nullptr;
other.control = nullptr;
}
// Copie depuis WeakPtr
explicit SharedPtr(const WeakPtr<T>& weak);
// Assignation copie
SharedPtr& operator=(const SharedPtr& other) {
if (this != &other) {
release();
ptr = other.ptr;
control = other.control;
if (control) control->strong_count++;
}
return *this;
}
// Assignation move
SharedPtr& operator=(SharedPtr&& other) noexcept {
if (this != &other) {
release();
ptr = other.ptr;
control = other.control;
other.ptr = nullptr;
other.control = nullptr;
}
return *this;
}
// Libère la ressource si plus de SharedPtr
void release() {
if (control) {
if (--control->strong_count == 0) {
delete ptr;
ptr = nullptr;
if (control->weak_count == 0)
delete control;
}
ptr = nullptr;
control = nullptr;
}
}
T* get() const { return ptr; }
T& operator*() const { return *ptr; }
T* operator->() const { return ptr; }
size_t use_count() const { return control ? control->strong_count : 0; }
explicit operator bool() const { return ptr != nullptr; }
};
// -------- WeakPtr --------
template <typename T>
class WeakPtr {
private:
ControlBlock<T>* control;
friend class SharedPtr<T>;
public:
WeakPtr() : control(nullptr) {}
// Constructeur depuis SharedPtr
WeakPtr(const SharedPtr<T>& shared)
: control(shared.control) {
if (control) control->weak_count++;
}
// Copie
WeakPtr(const WeakPtr& other)
: control(other.control) {
if (control) control->weak_count++;
}
// Move
WeakPtr(WeakPtr&& other) noexcept
: control(other.control) {
other.control = nullptr;
}
// Destructeur
~WeakPtr() {
release();
}
void release() {
if (control) {
if (--control->weak_count == 0 && control->strong_count == 0)
delete control;
control = nullptr;
}
}
// Crée un SharedPtr si l’objet existe encore
SharedPtr<T> lock() const {
if (control && control->strong_count > 0) {
control->strong_count++;
return SharedPtr<T>(*this); // appelle le constructeur privé
}
return SharedPtr<T>(); // nul
}
bool expired() const {
return !control || control->strong_count == 0;
}
};
// Constructeur SharedPtr depuis WeakPtr
template <typename T>
SharedPtr<T>::SharedPtr(const WeakPtr<T>& weak)
: ptr(nullptr), control(weak.control) {
if (control && control->strong_count > 0) {
ptr = control->ptr;
control->strong_count++;
} else {
control = nullptr;
}
}