liveBook · Manning

Circular reference

As explained in 100 C++ Mistakes and How to Avoid Them

A circular reference is a cycle of object references in which objects eventually refer back to an earlier object in the cycle.

A circular reference occurs when objects refer to one another through shared pointers, eventually returning to an earlier object. The resulting ownership cycle can keep every reference count above zero, so the resources are never deleted. section 2.9

Mistake: shared ownership in both directions

A doubly linked structure can create the problem when each node stores both prev and next as std::shared_ptr values. One node owns the next node, while that node owns the previous node; after the surrounding deque is destroyed, the cycle may keep the nodes alive. Prefer unique ownership where sharing is unnecessary, and replace one ownership direction with a weak pointer when the links must point both ways. section 2.9

Listing 2.18 A deque using shared pointers
class Node {
public:
    Node(int value) : data(value) {}
    int data;
    std::shared_ptr<Node> prev;                             #1
    std::shared_ptr<Node> next;                             #2
};
 
class Deque {
private:
    std::shared_ptr<Node> front;
    std::shared_ptr<Node> back;
public:
    Deque() : front(nullptr), back(nullptr) {}              #3
    void push_front(int value) {
        std::shared_ptr<Node> newNode = std::make_shared<Node>(value);
        if (front == nullptr)
            front = back = newNode;
        else {
            newNode->next = front;
            front->prev = newNode;
            front = newNode;
        }
    }
    void push_back(int value) {
        std::shared_ptr<Node> newNode = std::make_shared<Node>(value);
        if (back == nullptr)
            front = back = newNode;
        else {
            newNode->prev = back;
            back->next = newNode;
            back = newNode;
        }
    }
    void pop_front() {
        if (front != nullptr) {
            front = front->next;
            if (front != nullptr)
                front->prev = nullptr;
            else
                back = nullptr;
        }    #B
    }
    void pop_back() {
        if (back != nullptr) {
            back = back->prev;
            if (back != nullptr)
                back->next = nullptr;
            else
                front = nullptr;
        }                                                   #4
    }
};
 
int main() {
    Deque deque;
    deque.push_front(3);
    deque.pop_front();
    return 0;
}

Inspect the shared-pointer deque and its bidirectional node links, which provide a concrete setting for identifying circular ownership. section 2.9

How 100 C++ Mistakes and How to Avoid Them covers it

How deep it goes
Explains reference-count failure and recommends weak pointers, stopping short of a complete corrected implementation.
Examples
Uses a deque whose nodes contain shared prev and next pointers as the concrete circular-reference example.
What it assumes
Assumes basic pointers and dynamic resources while explaining shared-pointer ownership and reference counting.
What you can do after
Diagnose ownership cycles and select unique or weak pointers to prevent shared resources from leaking.
How it connects
Connects shared pointers, unique pointers, weak pointers, doubly linked lists, deques, and resource leaks.

Also explained in

Compare how 3 books explain circular reference →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 C++ Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.