Решить ссылку на цикл C 11 shared_ptr через сброс элемента shared_ptrs в деструкторе класса?

#c #c 11 #memory #memory-leaks #shared-ptr

#c #c 11 #память #утечки памяти #общий-ptr

Вопрос:

Мы знаем, что C 11 имеет shared_ptr, и у него будет проблема со ссылкой на цикл.

Я пытаюсь решить эту проблему, сбросив все элементы shared_ptrs в деструкторе класса.

Пример-1 share.cpp :

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

class A;
class B;

struct A {
  A(const std::string amp;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 amp;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;
}
  

Выполнить: clang -std=c 11 share.cpp amp;amp; ./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
  

Мы видим утечку памяти из-за ссылки на цикл. Итак, моя идея такова: я сбрасываю все элементы shared_ptrs во всех деструкторах моих классов. Тогда у нас есть пример-2 share.cpp :

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

class A;
class B;

struct A {
  A(const std::string amp;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 amp;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;
}
  

Выполнить clang -std=c 11 share.cpp amp;amp; ./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
  

Мы видим, что ссылка на цикл исправлена!

Могу ли я использовать такой шаблон проектирования для решения проблемы с ссылками на цикл shared_ptr в проекте C ? например, я просто сбрасываю все shared_ptrs в деструкторах классов в проекте C .

Комментарии:

1. При наличии ссылочного цикла ваши деструкторы никогда не будут запущены, поэтому изменение кода в деструкторах вам не поможет. Лучшим решением было бы решить, какие из ваших указателей являются указателями «обратного направления» (например, b-> a в цикле a-> b-> a) и сделать их типа weak_ptr или даже обычным указателем в стиле C, чтобы они не увеличивали количество ссылок.

Ответ №1:

Короткий ответ: Нет, потому shared_ptr::reset() что уменьшает количество использования так же, как это делает деструктор общего указателя.

Длинный ответ: прочитайте свои задания.

Давайте сначала рассмотрим случай A2 / B2. После вашего назначения у вас есть 2 «цепочки» объектов. B2-> A1->B2 и A2-> B1-> A2. Общие указатели A1 / B1 имеют значение use_count равное 2 (один для цепочки, один для локальной переменной). Количество использований общих указателей A2 / B2 равно 1 (для цепочки)

Итак, затем вы запускаете 3-й блок

 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
  

Теперь, когда это выходит за рамки, A3 / B3 по-прежнему указывают друг на друга и пропускаются. Единственными ссылками на объекты A1 / B1 являются локальные переменные, которые затем выходят за пределы области видимости.

Итак, вы видите, что объект B2, а затем объект A2 уничтожаются при выполнении назначений, а затем уничтожаются B1 и A1. Объекты A3 / B3 никогда не уничтожаются.

Комментарии:

1.вы правы, a3 b3 сообщение деструктора не выводится на консоль, поэтому их деструктор никогда не вызывается при выходе из области видимости. утечка памяти.