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;
    }
}