문제

shared_ptr은 Boost 라이브러리의 참조 계산 스마트 포인터입니다.

참조 카운팅의 문제점은 사이클을 처리할 수 없다는 것입니다.C++에서 이 문제를 어떻게 해결하는지 궁금합니다.

다음과 같은 제안은 하지 마세요."주기를 만들지 마십시오"또는 "weak_ptr을 사용하십시오".

편집하다

나는 단지 weak_ptr을 사용하라는 제안을 좋아하지 않습니다. 왜냐하면 분명히 당신이 순환을 생성할 것이라는 것을 안다면 문제가 없을 것이기 때문입니다.또한 런타임 중에 shared_ptrs를 생성하는 경우 컴파일 시간에 주기가 있다는 것을 알 수 없습니다.

그러니 제가 특별히 그런 종류의 답변을 갖지 말라고 요청했기 때문에 Weak_ptr을 사용하는 답변을 자체 삭제해 주세요...

도움이 되었습니까?

해결책

나는 큰 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
};

방금 문제의 클래스에 그것을 추가하고 레지스트리를 살펴보았습니다.

(ID는 예를 들어 다음과 같이 주어진 경우입니다.'xyz!' 문자열로 변환됩니다.안타깝게도 문자열 상수를 템플릿 매개변수로 지정할 수 없습니다.)

다른 팁

shared_ptr 나타냅니다 소유권 관계.하는 동안 weak_ptr 나타냅니다 의식.여러 객체가 서로 소유한다는 것은 아키텍처에 문제가 있음을 의미하며, 이는 하나 이상의 객체를 변경하여 해결됩니다. 소유하다~에 빠져있다 인식(즉, weak_ptr'에스).

왜 제안하는지 이해가 안가네요 weak_ptr 쓸모없는 것으로 간주됩니다.

나는 순환 참조를 깨기 위해 약한_ptr을 사용하라는 뻔한 지시를 받은 것에 대한 당신의 짜증을 이해하며 순환 참조가 나쁜 프로그래밍 스타일이라는 말을 들었을 때 나 자신도 거의 분노를 느낍니다.

순환 참조를 어떻게 발견하는지 구체적으로 물어보십시오.사실은 복잡한 프로젝트에서 일부 참조 순환이 간접적이고 발견하기 어렵다는 것입니다.

대답은 순환 참조에 취약해지는 잘못된 선언을 해서는 안 된다는 것입니다.나는 진지하게 모든 것에 대해 맹목적으로 shared_ptr을 사용하는 매우 인기 있는 관행을 비판하고 있습니다.

설계 시 어느 포인터가 소유자이고 어느 포인터가 관찰자인지 명확해야 합니다.

소유자의 경우 shared_ptr을 사용하십시오.

관찰자의 경우 약한_ptr을 사용하십시오. 사이클의 일부일 수 있다고 생각하는 것뿐만 아니라 모든 것입니다.

이 방법을 따르면 순환 참조로 인해 문제가 발생하지 않으며 걱정할 필요가 없습니다.물론 이러한 모든 Weak_ptrs를 사용하고 싶을 때 shared_ptrs로 변환하기 위해 작성해야 할 코드가 많이 있을 것입니다. Boost는 실제로 해당 작업에 적합하지 않습니다.

주기를 감지하는 것은 매우 쉽습니다.

  • 개수를 1000과 같이 큰 숫자로 설정합니다(정확한 크기는 애플리케이션에 따라 다름).
  • 관심 있는 항목부터 시작하여 해당 항목의 지침을 따르세요.
  • 따르는 각 포인터에 대해 개수를 줄입니다.
  • 포인터 체인의 끝에 도달하기 전에 카운트가 0으로 떨어지면 사이클이 발생합니다.

그러나 그다지 유용하지는 않습니다.그리고 참조 계산 포인터에 대한 순환 문제를 해결하는 것은 일반적으로 불가능합니다. 이것이 바로 세대 청소와 같은 대체 가비지 수집 방식이 발명된 이유입니다.

다음의 조합 boost::weak_ptr 그리고 boost::shared_ptr 아마도? 이것 기사가 흥미로울 수 있습니다.

이 게시물을 참조하세요 주기 감지 그래프에서.

사이클을 찾는 일반적인 솔루션은 다음에서 찾을 수 있습니다.

연결된 목록에 순환이 있는지 테스트하는 최고의 알고리즘

이는 사용자가 목록에 있는 개체의 구조를 알고 있고 각 개체에 포함된 모든 포인터를 따를 수 있다고 가정합니다.

아마도 다음과 같은 Garbage Collector 기술이 필요할 것입니다. 마크 앤 스윕.이 알고리즘의 아이디어는 다음과 같습니다.

  1. 할당된 모든 메모리 블록에 대한 참조 목록을 유지합니다.
  2. 어느 시점에서 가비지 수집기를 시작합니다.
    1. 먼저 참조 목록을 사용하지 않고도 액세스할 수 있는 모든 블록을 표시합니다.
    2. 표시할 수 없는 각 항목을 지우는 목록을 살펴보는데, 이는 해당 항목에 더 이상 접근할 수 없으므로 유용하지 않음을 의미합니다.

사용하고 계시기 때문에 shared_ptr 도달하지 못한 기존 포인터는 모두 주기의 구성원으로 간주되어야 합니다.

구현

아래에서는 구현 방법에 대한 매우 순진한 예를 설명합니다. sweep() 알고리즘의 일부이지만 reset() 수집기에 남아 있는 모든 포인터.

이 코드는 저장합니다 shared_ptr<Cycle_t> 포인터.클래스 Collector 모든 포인터를 추적하고 다음과 같은 경우 이를 삭제할 책임이 있습니다. 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처럼 작동) Mark and Sweep 알고리즘이 이미 작업을 처리하고 있으므로 이러한 Garbage Collector를 구현합니다.
  3. mark() 함수는 예제가 복잡해질 수 있으므로 구현하지 않았지만 가능하며 아래에서 설명하겠습니다.

마지막으로 (2)에 관심이 있다면 이런 종류의 구현은 전례가 없습니다.CPython(Python의 주요 구현)은 참조 계산과 Mark 및 Sweep을 혼합하여 사용하지만 대부분은 역사적 이유.

구현 mark() 기능:

구현하려면 mark() 기능을 사용하려면 다음과 같이 몇 가지 수정이 필요합니다.

다음을 추가해야 합니다. bool marked; ~에 귀속하다 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 없음"이라고 말한 것을 알고 있지만 왜 안 됩니까?머리에서 꼬리까지의 약한_ptr을 갖고 꼬리에서 머리까지의 약한_ptr을 갖는 것은 순환을 방지합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top