Solve C++11 shared_ptr cycle reference through reset member shared_ptrs in class destructor?

Viewed 62

We know that C++11 has shared_ptr and it will has cycle reference issue.

I try to solve this issue by reset all member shared_ptrs in class destructor.

Example-1 share.cpp :

#include <iostream>
#include <memory>
#include <string>

class A;
class B;

struct A {
  A(const std::string &a_name) : name(a_name), b(nullptr) {
    std::cout << name << " A::A b.get:" << b.get()
              << ", b.use_count:" << b.use_count() << std::endl;
  }
  ~A() {
    std::cout << name << " A::~A b.get:" << b.get()
              << ", b.use_count:" << b.use_count() << std::endl;
  }
  std::string name;
  std::shared_ptr<B> b;
};

struct B {
  B(const std::string &a_name) : name(a_name), a(nullptr) {
    std::cout << name << " B::B a.get:" << a.get()
              << ", a.use_count:" << a.use_count() << std::endl;
  }
  ~B() {
    std::cout << name << " B::~B a.get:" << a.get()
              << ", a.use_count:" << a.use_count() << std::endl;
  }
  std::string name;
  std::shared_ptr<A> a;
};

int main(void) {
  std::shared_ptr<A> a1(new A("a1"));
  std::shared_ptr<B> b1(new B("b1"));
  a1->b = b1;
  b1->a = a1;
  {
    std::shared_ptr<A> a2(new A("a2"));
    std::shared_ptr<B> b2(new B("b2"));
    a2->b = b2;
    b2->a = a2;
    a1->b = b2;
    b1->a = a2;
    a2->b = b1;
    b2->a = a1;
  }
  {
    std::shared_ptr<A> a3(new A("a3"));
    std::shared_ptr<B> b3(new B("b3"));
    a3->b = b1;
    b3->a = a1;
    a1->b = b3;
    b1->a = a3;
    a3->b = b3;
    b3->a = a3;
  }
  return 0;
}

Run: clang++ -std=c++11 share.cpp && ./a.out:

a1 A::A b.get:0x0, b.use_count:0
b1 B::B a.get:0x0, a.use_count:0
a2 A::A b.get:0x0, b.use_count:0
b2 B::B a.get:0x0, a.use_count:0
a3 A::A b.get:0x0, b.use_count:0
b3 B::B a.get:0x0, a.use_count:0
b2 B::~B a.get:0x7ff393405900, a.use_count:3
a2 A::~A b.get:0x7ff393405950, b.use_count:3
b1 B::~B a.get:0x7ff393405a40, a.use_count:2
a1 A::~A b.get:0x7ff393405a90, b.use_count:2

We can see there's memory leak due to cycle reference. So my idea is: I reset all member shared_ptrs in all my classes destructors. Then we have Example-2 share.cpp:

#include <iostream>
#include <memory>
#include <string>

class A;
class B;

struct A {
  A(const std::string &a_name) : name(a_name), b(nullptr) {
    std::cout << name << " A::A b.get:" << b.get()
              << ", b.use_count:" << b.use_count() << std::endl;
  }
  ~A() {
    std::cout << name << " A::~A before reset b.get:" << b.get()
              << ", b.use_count:" << b.use_count() << std::endl;
    b.reset();
    std::cout << name << " A::~A after reset b.get:" << b.get()
              << ", b.use_count:" << b.use_count() << std::endl;
  }
  std::string name;
  std::shared_ptr<B> b;
};

struct B {
  B(const std::string &a_name) : name(a_name), a(nullptr) {
    std::cout << name << " B::B a.get:" << a.get()
              << ", a.use_count:" << a.use_count() << std::endl;
  }
  ~B() {
    std::cout << name << " B::~B before reset a.get:" << a.get()
              << ", a.use_count:" << a.use_count() << std::endl;
    a.reset();
    std::cout << name << " B::~B after reset a.get:" << a.get()
              << ", a.use_count:" << a.use_count() << std::endl;
  }
  std::string name;
  std::shared_ptr<A> a;
};

int main(void) {
  std::shared_ptr<A> a1(new A("a1"));
  std::shared_ptr<B> b1(new B("b1"));
  a1->b = b1;
  b1->a = a1;
  {
    std::shared_ptr<A> a2(new A("a2"));
    std::shared_ptr<B> b2(new B("b2"));
    a2->b = b2;
    b2->a = a2;
    a1->b = b2;
    b1->a = a2;
    a2->b = b1;
    b2->a = a1;
  }
  {
    std::shared_ptr<A> a3(new A("a3"));
    std::shared_ptr<B> b3(new B("b3"));
    a3->b = b1;
    b3->a = a1;
    a1->b = b3;
    b1->a = a3;
    a3->b = b3;
    b3->a = a3;
  }
  return 0;
}

Run clang++ -std=c++11 share.cpp && ./a.out:

a1 A::A b.get:0x0, b.use_count:0
b1 B::B a.get:0x0, a.use_count:0
a2 A::A b.get:0x0, b.use_count:0
b2 B::B a.get:0x0, a.use_count:0
a3 A::A b.get:0x0, b.use_count:0
b3 B::B a.get:0x0, a.use_count:0
b2 B::~B before reset a.get:0x7fdf23405900, a.use_count:3
b2 B::~B after reset a.get:0x0, a.use_count:0
a2 A::~A before reset b.get:0x7fdf23405950, b.use_count:3
a2 A::~A after reset b.get:0x0, b.use_count:0
b1 B::~B before reset a.get:0x7fdf23405a40, a.use_count:2
b1 B::~B after reset a.get:0x0, a.use_count:0
a1 A::~A before reset b.get:0x7fdf23405a90, b.use_count:2
a1 A::~A after reset b.get:0x0, b.use_count:0

We see the cycle reference is fixed!

Can I use such a design pattern to solve shared_ptr cycle reference issue in C++ project ? e.g. I just reset all the shared_ptrs in the classes destructors in a C++ project.

1 Answers

Short answer: No, because shared_ptr::reset() reduces the use count in the same way the shared pointer destructor does.

Long answer: Read your assignments.

Let's take the A2/B2 case first. After your assignment you have 2 'chains' of objects. B2->A1->B2 and A2->B1->A2. The A1/B1 shared pointers have a use_count of 2 (one for the chain, one for the local variable). The A2/B2 shared pointers have a use count of 1 (for the chain)

So then you start the 3rd block

a1->b = b3; // After this assignment, 
            // you've broken the A1->B2 chain, so now B2's use 
            // count is zero and will be destroyed
b1->a = a3; // Same as above, for the B1->A2 chain, destroying A2 
a3->b = b3; // This just assigns A3 to B3
b3->a = a3; // And back B3, forming the A3->B3->A3 chain

Now, when this goes out of scope, A3/B3 still point to each other, and are leaked. The only references to the A1/B1 objects are the local variables, which then go out of scope.

So you see the B2 and then A2 object destroyed as you perform the assignments, and then B1 and A1 are destroyed. The A3/B3 objects are never destroyed.

Related