Skip to content

63 Intermediate The Hundred

The Reference Count That Never Reached Zero

two objects, each politely waiting for the other to leave first

struct Node {
    std::string name;
    std::shared_ptr<Node> child;
    std::shared_ptr<Node> parent;
    explicit Node(std::string n) : name(std::move(n)) {}
    ~Node() { std::cout << "destroying " << name << "\n"; }
};

int main() {
    {
        auto root = std::make_shared<Node>("root");
        auto leaf = std::make_shared<Node>("leaf");
        root->child = leaf;
        leaf->parent = root;   // every child should know its parent
        std::cout << "root use_count " << root.use_count() << ", leaf use_count "
                  << leaf.use_count() << "\n";
    }
    std::cout << "scope closed\n";
}

Run it. Which destructors run when the inner scope ends?

Answer

None of them. Two smart pointers, two nodes, zero destructors:

root use_count 2, leaf use_count 2
scope closed

Why

Each make_shared starts a count at 1; then root->child = leaf lifts leaf to 2 and leaf->parent = root lifts root to 2 — that pair of twos, printed a line before it bites, is the bug. At scope exit the locals each release one reference, leaving root at 1 (held by leaf->parent) and leaf at 1 (held by root->child): neither hits zero, so neither destructor runs and the pair keeps itself alive with nobody left who can reach it. Reference counting is a purely local rule — it can tell that nothing points at one object, never that nothing points into a group. Not undefined behavior, and -Wall -Wextra are silent (GCC 11.5) — just a quiet leak, which valgrind --leak-check=full reports as 160 (80 direct, 80 indirect) bytes ... definitely lost.

The fix

Let the back-pointer observe instead of own — a weak_ptr adds nothing to the count, and .lock() hands you a shared_ptr on the occasions you really need the parent:

std::weak_ptr<Node> parent;   // observes, does not own

Change that one line and root's count prints as 1; at scope exit root is destroyed first, and releasing its child takes leaf with it. Owning pointers must form a tree.

Takeaway: reference counting cannot collect cycles — break them by design, with weak_ptr on the links that point back.

Try it: g++ -std=c++17 main.cpp -o demo && ./demo

Open in Compiler Explorer ↗ Quiz this entry