c++c++11destructorsmart-pointers

Unexpected Order of Destructor Calls with std::shared_ptr and std::weak_ptr in C++


Question:

I have a C++ program using std::shared_ptr and std::weak_ptr involving two classes, A and B. The program creates shared pointers for both classes and sets up a relationship between them, with A holding a weak pointer to B, and B holding a shared pointer to A. According to my understanding, the destructors should be called in the reverse order of the stack calls. However, the output indicates that B's destructor is called before A's, which seems unusual to me.

Here is the code snippet:

#include <iostream>
#include <memory>

class B;

class A {
public:
    std::weak_ptr<B> b_weak_ptr;
    ~A() {
        std::cout << "A destructor called" << std::endl;
    }
};

class B {
public:
    std::shared_ptr<A> a_ptr;
    ~B() {
        std::cout << "B destructor called" << std::endl;
    }
};

int main() {
    {
        std::shared_ptr<B> b = std::make_shared<B>();
        std::shared_ptr<A> a = std::make_shared<A>();

        a->b_weak_ptr = b;
        b->a_ptr = a;
    }
    return 0;
}

Output:

B destructor called
A destructor called

Can anyone explain why the destructor for B is being called before the destructor for A?

I initially suspected compiler optimizations might be affecting the order of destructor calls, but checks indicated otherwise.

image


Solution

  • a is destroyed first, but that only reduces the reference count to the A instance by one. There is still one reference to it in b.a_ptr so the A instance is not destroyed. When b is later destroyed (and a_ptr with it), the reference count to the A instance becomes zero, and then As destructor is called.

    A small modification of your code to show it more clearly:

    int main() {
        std::shared_ptr<B> b = std::make_shared<B>();
        {
            std::shared_ptr<A> a = std::make_shared<A>();
    
            a->b_weak_ptr = b;
            b->a_ptr = a;
            std::cout << b->a_ptr.use_count() << '\n';  // prints 2
        }                                               // a is destroyed
        std::cout << b->a_ptr.use_count() << '\n';      // prints 1
    }                                                   // b is destroyed
    

    Output:

    2
    1
    B destructor called
    A destructor called