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