Как определить циклы при использовании общего ptr
shared_ptr-это интеллектуальный указатель подсчета ссылок в библиотеке Boost.
проблема с подсчетом ссылок заключается в том, что он не может распоряжаться циклов. Мне интересно, как можно было бы решить это на C++.
пожалуйста, никаких предложений, таких как:" не делайте циклов "или"используйте weak_ptr".
редактировать
Мне не нравятся предложения, которые говорят просто использовать weak_ptr, потому что, очевидно, если вы знаете, что создадите цикл, то вы не будет проблем. Вы также не можете знать, что у вас будет цикл во время компиляции, если вы создадите shared_ptrs во время выполнения.
поэтому, пожалуйста, удалите ответы, которые используют weak_ptr в них, потому что я специально попросил не иметь таких ответов...
10 ответов
Я не нашел намного лучшего метода, чем рисование больших графов UML и поиск циклов.
для отладки я использую счетчик экземпляра, идущий в реестр, например:
template <DWORD id>
class CDbgInstCount
{
public:
#ifdef _DEBUG
   CDbgInstCount()   { reghelper.Add(id, 1); }
   CDbgInstCount(CDbgInstCount const &) {  reghelper.Add(id, 1); }
   ~CDbgInstCount()  { reghelper.Add(id, -1); }
#else
#endif
};
Я просто нед, чтобы добавить это к рассматриваемым классам и посмотреть на реестр.
(идентификатор, если задан как, например, ' XYZ! будет преобразован в строку. К сожалению, вы не можете указать строковую константу в качестве параметра шаблона)
shared_ptr представляет собственности отношения. В то время как weak_ptr представляет осознание. Наличие нескольких объектов, владеющих друг другом, означает, что у вас есть проблемы с архитектурой, которые решаются путем изменения одного или нескольких собственныев курсе(то есть,weak_ptr ' s).
Я не понимаю, почему предлагаю weak_ptr является бесполезным.
Я понимаю ваше раздражение от того, что вам Бойко сказали использовать weak_ptr для разрыва циклических ссылок, и сам я почти чувствую ярость, когда мне говорят, что циклические ссылки-плохой стиль программирования.
ваш спросите конкретно, как вы определяете циклические ссылки. Правда в том, что в сложном проекте некоторые опорные циклы являются косвенными и их трудно обнаружить.
ответ заключается в том, что вы не должны делать ложные объявления, которые оставляют вас уязвимыми для циклических ссылок. Я серьезно, и я критикую очень популярную практику-слепо использовать shared_ptr для всего.
в вашем дизайне должно быть ясно, какие указатели являются владельцами, а какие наблюдателями.
для владельцев используйте shared_ptr
для наблюдателей используйте weak_ptr - все они, а не только те, которые, как вы думаете, могут быть частью цикла.
Если вы будете следовать этой практике, то циклические ссылки не вызовут никаких проблем, и вам не нужно беспокоиться о них. Из конечно, у вас будет много кода для преобразования всех этих weak_ptrs в shared_ptrs, когда вы хотите их использовать - Boost действительно не подходит для работы.
довольно легко обнаружить циклы:
- установите счетчик на некоторое большое число, скажем 1000 (точный размер зависит от вашего приложения)
 - начнем с pionter, которые вас интересуют и следить за указателями от него
 - для каждого указателя вы следуете, уменьшить количество
 - если количество падает до нуля, прежде чем вы достигнете конца цепочки указателей, у вас есть цикл
 
Это, однако, не очень полезно. И это не как правило, можно решить проблему cycvle для ref-подсчитанных указателей-вот почему были изобретены альтернативные схемы сбора мусора, такие как генерация очистки.
сочетание boost::weak_ptr и boost::shared_ptr может быть? этой статья может быть интересна.
общее решение для поиска цикла можно найти здесь:
лучший алгоритм для проверки, если связанный список имеет цикл
Это предполагает, что вы знаете структуру объектов в списке, и все указатели содержащихся в каждом объекте.
вы, вероятно, нуждаетесь в технике сборщика мусора, такой как Марк и развертки. Идея этого алгоритма:
- сохранить список со ссылками на все выделенные блоки памяти.
 - в какой-то момент Вы начинаете сборщик мусора:
- он сначала отмечает все блоки, к которым он может получить доступ, не используя список ссылок.
 - он проходит через список, стирая каждый элемент, который не может быть отмечен, подразумевая его больше не доступен, поэтому он не полезен.
 
 
если вы используете shared_ptr любые все еще существующие указатели, которые вы не можете достичь, должны рассматриваться как члены цикла.
реализация
ниже я описываю очень наивный пример того, как реализовать sweep() часть алгоритма, но это будет reset() все остальные указатели на коллектор.
этот код хранит shared_ptr<Cycle_t> указатели. Класс!--8--> is отвечает за отслеживание всех указателей и их удаление, когда sweep() выполняется.
#include <vector>
#include <memory>
class Cycle_t;
typedef std::shared_ptr<Cycle_t> Ref_t;
// struct Cycle;
struct Cycle_t {
  Ref_t cycle;
  Cycle_t() {}
  Cycle_t(Ref_t cycle) : cycle(cycle) {}
};
struct collector {
  // Note this vector will grow endlessy.
  // You should find a way to reuse old links
  std::vector<std::weak_ptr<Cycle_t>> memory;
  // Allocate a shared pointer keeping
  // a weak ref on the memory vector:
  inline Ref_t add(Ref_t ref) {
    memory.emplace_back(ref);
    return ref;
  }
  inline Ref_t add(Cycle_t value) {
    Ref_t ref = std::make_shared<Cycle_t>(value);
    return add(ref);
  }
  inline Ref_t add() {
    Ref_t ref = std::make_shared<Cycle_t>();
    return add(ref);
  }
  void sweep() {
    // Run a sweep algorithm:
    for (auto& ref : memory) {
      // If the original shared_ptr still exists:
      if (auto ptr = ref.lock()) {
        // Reset each pointer contained within it:
        ptr->cycle.reset();
        // Doing this will trigger a deallocation cascade, since
        // the pointer it used to reference will now lose its
        // last reference and be deleted by the reference counting
        // system.
        //
        // The `ptr` pointer will not be deletd on the cascade
        // because we still have at least the current reference
        // to it.
      }
      // When we leave the loop `ptr` loses its last reference
      // and should be deleted.
    }
  }
};
вы можете использовать его как это:
Collector collector;
int main() {
  // Build your shared pointers:
  {
    // Allocate them using the collector:
    Ref_t c1 = collector.add();
    Ref_t c2 = collector.add(c1);
    // Then create the cycle:
    c1.get()->cycle = c2;
    // A normal block with no cycles:
    Ref_t c3 = collector.add();
  }
  // In another scope:
  {
    // Note: if you run sweep an you still have an existing
    // reference to one of the pointers in the collector
    // you will lose it since it will be reset().
    collector.sweep();
  }
}
я тестировал его с Valgrind, и никаких утечек памяти или" все еще достижимых " блоков не было, поэтому он, вероятно, работает так, как ожидалось.
некоторые заметки об этой реализации:
- вектор памяти будет расти бесконечно, вы должны использовать некоторые методы выделения памяти чтобы убедиться, что он не занимает всю вашу рабочую память.
 - можно утверждать, что нет необходимости использовать 
shared_ptr(это работает как GC подсчета ссылок) для реализации такого сборщика мусора, так как алгоритм метки и развертки уже позаботится о работе. - я не реализовал функцию mark (), потому что это усложнило бы пример, но это возможно, и я объясню это ниже.
 
наконец, если вас интересует (2), это вид реализации не слыхивали. CPython (основная реализация Python) использует смесь подсчета ссылок и метки и развертки, но в основном для исторические причины.
реализация :
для реализации  до Cycle_t, и используйте его, чтобы проверить, отмечен ли указатель или не.
нужно писать Collector::mark() функция, которая будет выглядеть так:
void mark(Ref_t root) {
  root->marked = true;
  // For each other Ref_t stored on root:
  for (Ref_t& item : root) {
    mark(item);
  }
}
и затем вы должны изменить sweep() функция для удаления метки, если указатель отмечен или еще reset() указатели:
void sweep() {
  // Run a sweep algorithm:
  for (auto& ref : memory) {
    // If it still exists:
    if (auto ptr = ref.lock()) {
      // And is marked:
      if (ptr->marked) {
        ptr->marked = false;
      } else {
        ptr->cycle.reset();
      }
    }
  }
}
это было длинное объяснение, но я надеюсь, что это помогает кто-то.
ответ на старый вопрос, вы можете попробовать навязчивый указатель, который может помочь подсчитать, сколько раз ресурс упоминается.
#include <cstdlib>
#include <iostream>
#include <boost/intrusive_ptr.hpp>
class some_resource
{
    size_t m_counter;
public:
    some_resource(void) :
        m_counter(0)
    {
        std::cout << "Resource created" << std::endl;
    }
    ~some_resource(void)
    {
        std::cout << "Resource destroyed" << std::endl;
    }
    size_t refcnt(void)
    {
        return m_counter;
    }
    void ref(void)
    {
        m_counter++;
    }
    void unref(void)
    {
        m_counter--;
    }
};
void
intrusive_ptr_add_ref(some_resource* r)
{
    r->ref();
    std::cout << "Resource referenced: " << r->refcnt()
              << std::endl;
}
void
intrusive_ptr_release(some_resource* r)
{
    r->unref();
    std::cout << "Resource unreferenced: " << r->refcnt()
              << std::endl;
    if (r->refcnt() == 0)
        delete r;
}
int main(void)
{
    boost::intrusive_ptr<some_resource> r(new some_resource);
    boost::intrusive_ptr<some_resource> r2(r);
    std::cout << "Program exiting" << std::endl;
    return EXIT_SUCCESS;
}
вот результат возвращается.
Resource created 
Resource referenced: 1 
Resource referenced: 2 
Program exiting 
Resource unreferenced: 1
Resource unreferenced: 0 
Resource destroyed
*** Program Exit ***
Я знаю, что вы сказали "нет weak_ptr", но почему бы и нет? Наличие вашей головы с weak_ptr для хвоста, а хвост с weak_ptr для головы предотвратит цикл.