Как определить циклы при использовании общего 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 может быть? этой статья может быть интересна.


писать обнаружение циклов в графе.


общее решение для поиска цикла можно найти здесь:

лучший алгоритм для проверки, если связанный список имеет цикл

Это предполагает, что вы знаете структуру объектов в списке, и все указатели содержащихся в каждом объекте.


вы, вероятно, нуждаетесь в технике сборщика мусора, такой как Марк и развертки. Идея этого алгоритма:

  1. сохранить список со ссылками на все выделенные блоки памяти.
  2. в какой-то момент Вы начинаете сборщик мусора:
    1. он сначала отмечает все блоки, к которым он может получить доступ, не используя список ссылок.
    2. он проходит через список, стирая каждый элемент, который не может быть отмечен, подразумевая его больше не доступен, поэтому он не полезен.

если вы используете 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, и никаких утечек памяти или" все еще достижимых " блоков не было, поэтому он, вероятно, работает так, как ожидалось.

некоторые заметки об этой реализации:

  1. вектор памяти будет расти бесконечно, вы должны использовать некоторые методы выделения памяти чтобы убедиться, что он не занимает всю вашу рабочую память.
  2. можно утверждать, что нет необходимости использовать shared_ptr (это работает как GC подсчета ссылок) для реализации такого сборщика мусора, так как алгоритм метки и развертки уже позаботится о работе.
  3. я не реализовал функцию 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 для головы предотвратит цикл.