std :: weak_ptr은 언제 유용합니까?


답변:


231

좋은 예는 캐시입니다.

최근에 액세스 한 객체의 경우 메모리에 유지하려는 경우 객체에 대한 강력한 포인터를 갖습니다. 주기적으로 캐시를 스캔하고 최근에 액세스하지 않은 개체를 결정합니다. 메모리에 보관할 필요가 없으므로 강력한 포인터를 제거하십시오.

그러나 그 객체가 사용 중이고 다른 코드가 그것에 대한 강력한 포인터를 가지고 있다면 어떨까요? 캐시가 객체에 대한 유일한 포인터를 제거하면 다시는 찾을 수 없습니다. 따라서 캐시는 메모리에 남아 있는지 확인해야하는 객체에 대한 약한 포인터를 유지합니다.

이것은 약한 포인터가하는 일입니다. 여전히 물체가 있으면 물체를 찾을 수 있지만 다른 물체가 필요하지 않으면 주위를 유지하지 않습니다.


8
따라서 std :: wake_ptr은 다른 포인터가 가리키는 곳만 가리킬 수 있으며 가리키는 객체가 더 이상 다른 포인터에 의해 삭제되거나 더 이상 가리 키지 않을 때 nullptr을 가리 킵니까?

27
@RM : 기본적으로 그렇습니다. 포인터가 약한 경우 포인터를 강한 포인터로 승격시킬 수 있습니다. 해당 개체가 여전히 존재하면 (적어도 하나의 강력한 포인터가 여전히 존재하기 때문에) 해당 작업이 성공하고 해당 개체에 대한 강력한 포인터를 제공합니다. 해당 개체가 존재하지 않으면 (강한 포인터가 모두 없어지기 때문에) 해당 작업이 실패합니다 (일반적으로 약한 포인터를 버리면 반응합니다).
David Schwartz

12
강력한 포인터는 객체를 살아있는 상태로 유지하지만 weak_ptr은 객체의 수명 시간을 방해하지 않고 객체를 볼 수 있습니다.
Vivandiere

3
적어도 몇 번 사용했던 또 다른 예는 옵저버를 구현할 때 주제에 약한 포인터 목록을 유지하고 자체 목록 정리를 수행하는 것이 편리 해지는 경우가 있습니다. 관찰자가 삭제 될 때 명시 적으로 제거하는 데 약간의 노력을 절약 할 수 있으며, 관찰자를 파괴 할 때 사용 가능한 주제에 대한 정보가 없어도됩니다.
Jason C

3
잠깐, 캐시에서 shared_ptr을 유지하고 메모리에서 지워야 할 때 목록에서 제거하는 데 문제가 있습니까? 모든 사용자는 shared_ptr을 모두 동일하게 유지하며 모든 사용자가 작업을 마치면 캐시 된 리소스가 지워집니다.
rubenvb

299

std::weak_ptr매달려있는 포인터 문제 를 해결하는 아주 좋은 방법 입니다. 원시 포인터 만 사용하면 참조 된 데이터의 할당이 해제되었는지 알 수 없습니다. 대신, std::shared_ptr데이터를 관리하고 std::weak_ptr사용자에게 데이터를 제공함으로써 사용자는 expired()또는 을 호출하여 데이터의 유효성을 확인할 수 있습니다 lock().

std::shared_ptr모든 std::shared_ptr인스턴스가 제거되기 전에 제거되지 않은 데이터의 소유권을 모든 인스턴스가 공유 하므로 단독으로 이를 수행 할 수 없습니다 std::shared_ptr. 다음을 사용하여 매달린 포인터를 확인하는 방법의 예는 다음과 같습니다 lock().

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}

1
좋아, 마치 로컬에 (소유) 포인터를 널 (메모리 삭제)로 설정 한 것처럼, 같은 메모리에 대한 다른 (약한) 포인터도 모두 널로 설정되어있는 것과 같다
Pat-Laugh

std::weak_ptr::lockstd::shared_ptr관리 대상 개체의 소유권을 공유 하는 새 항목 을 만듭니다 .
Sahib Yar 12

129

또 다른 대답, 희망적으로 더 간단합니다. (동료 Google 직원)

가지고 Team있고 Member물건 을 가지고 있다고 가정하십시오 .

분명히 그것은 관계입니다 : Team객체는에 대한 포인터를 갖습니다 Members. 그리고 멤버들도 그들의 Team객체에 대한 백 포인터를 가질 것 입니다.

그런 다음 종속성주기가 있습니다. 을 사용 shared_ptr하면 주기적으로 서로 참조하기 때문에 객체에 대한 참조를 포기할 때 더 이상 객체가 자동으로 해제되지 않습니다. 이것은 메모리 누수입니다.

을 사용하여이 문제를 해결하십시오 weak_ptr. 일반적으로 "소유자"는 "소유자"를 사용 shared_ptr하고 "소유자" weak_ptr는 부모 에게 액세스를 필요로 할 때 일시적으로 변환합니다 shared_ptr.

약한 ptr을 저장하십시오.

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

그런 다음 필요할 때 사용하십시오

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes, it may fail if the parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope

1
이것이 어떻게 메모리 누수입니까? 팀이 파괴되면 멤버를 파괴하므로 shared_ptr 참조 횟수는 0이되고 파괴됩니까?
paulm

4
@paulm 팀은 "그"회원을 파괴하지 않습니다. 요점shared_ptr 소유권을 공유하는 것이므로 아무도 메모리를 해제 할 특별한 책임이 없으므로 더 이상 사용하지 않으면 자동으로 해제됩니다. 루프가없는 한 ... 여러 팀이 같은 플레이어를 공유 할 수 있습니다 (과거 팀?). 팀 오브젝트가 멤버를 "소유"하는 경우, shared_ptr시작 하기 위해 a 를 사용할 필요가 없습니다 .
Offirmo

1
그것들을 파괴하지는 않지만 그것의 shared_ptr은 그것의 범위를 벗어나고, use_count를 감소시킵니다. 따라서이 시점에서 use_count는 0이므로 shared_ptr은 가리키는 것을 삭제합니까?
paulm

2
@ paulm 당신이 맞아요. 그러나이 예에서 팀은 shared_ptr"팀원"에 의해 참조되기 때문에 언제 파괴됩니까? 당신이 설명하는 것은 루프가없는 경우입니다.
Offirmo

14
그렇게 나쁘지 않다고 생각합니다. 멤버가 여러 팀에 속할 수있는 경우 참조를 사용하면 효과가 없습니다.
Mazyod

22

다음은 @jleahy가 제공 한 한 가지 예입니다. 작업 모음이 있고 비동기 적으로 실행되고에 의해 관리된다고 가정합니다 std::shared_ptr<Task>. 이러한 작업으로 주기적으로 작업을 수행 할 수 있으므로 타이머 이벤트가 통과 std::vector<std::weak_ptr<Task>>하여 작업에 수행 할 작업을 제공 할 수 있습니다. 그러나 동시에 작업이 더 이상 필요없고 죽었다고 동시에 결정했을 수 있습니다. 따라서 타이머는 약한 포인터에서 공유 포인터를 만들고 공유 포인터가 null이 아닌 경우 해당 공유 포인터를 사용하여 작업이 아직 살아 있는지 확인할 수 있습니다.


4
좋은 예와 비슷하지만 좀 더 자세히 설명해 주시겠습니까? 작업이 완료되면 주기적으로 확인하지 않고 std :: vector <std :: weak_ptr <Task >>에서 이미 제거해야한다고 생각합니다. std :: vector <std :: weak_ptr <>>이 여기에 매우 도움이되는지 확실하지 않습니다.
Gob00st

대기열과 비슷한 의견 : 객체가 있다고 말하고 일부 리소스를 위해 대기열에 넣으면 대기 중 객체가 삭제 될 수 있습니다. 따라서 weak_ptrs를 대기열에 넣는 경우 해당 대기열에서 항목을 삭제하지 않아도됩니다. Weak_ptrs는 무효화되고 encoutner되면 폐기됩니다.
zzz777

1
@ zzz777 : 개체를 무효화하는 논리는 관찰자 또는 대기열의 존재를 인식하지 못할 수도 있습니다. 따라서 관찰자는 약한 포인터에 대해 별도의 루프를 수행하여 여전히 살아있는 포인터에 작용하고 컨테이너에서 죽은 포인터를 제거합니다 ...
Kerrek SB

1
@KerekSB : 예. 대기열의 경우 별도의 루프가 필요하지 않습니다. 자원을 사용할 수 있습니다. 유효한 유효 항목이있을 때까지 만료 된 weak_ptrs (있는 경우)를 버립니다.
zzz777

스레드가 컬렉션에서 스스로 제거되도록 할 수도 있지만 종속성을 생성하고 잠금이 필요합니다.
curiousguy

16

비동기 핸들러가 호출 될 때 대상 오브젝트가 여전히 존재한다고 보장 할 수없는 경우 Boost.Asio에 유용합니다. 트릭은 또는 람다 캡처를 weak_ptr사용하여 비동기 처리기 객체에 a를 바인딩하는 것 std::bind입니다.

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

이는 self = shared_from_this()보류중인 비동기 처리기가 대상 객체의 수명을 연장 하지는 않지만 대상 객체가 삭제 된 경우에도 여전히 안전합니다.


왜이 답변을 찾는 데 시간이 오래 걸렸나요? PS 당신은 당신의 캡처를 사용하지 않습니다this
Orwellophile

@ Orwellophile이 수정되었습니다. self = shared_from_this()핸들러가 동일한 클래스 내에서 메소드를 호출 할 때 관용구를 사용할 때 습관이 발생 합니다.
Emile Cormier

16

shared_ptr : 실제 객체를 보유합니다.

weak_ptr : lock실제 소유자에 연결하거나 shared_ptr그렇지 않으면 NULL을 반환합니다.

약한 ptr

대략적으로 말하면, weak_ptr역할은 주택 에이전시 의 역할과 유사합니다 . 에이전트가 없으면 집을 임대하기 위해 도시의 임의 주택을 확인해야 할 수도 있습니다. 에이전트는 여전히 접근 가능 하고 임대 가능한 주택 만 방문하도록합니다 .


14

weak_ptr특히 단위 테스트에서 객체의 올바른 삭제를 확인하는 것이 좋습니다. 일반적인 사용 사례는 다음과 같습니다.

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());

13

포인터를 사용할 때는 사용 가능한 여러 유형의 포인터와 각 포인터를 사용하는 것이 합리적 일 때를 이해하는 것이 중요합니다. 다음과 같이 두 가지 범주에 네 가지 유형의 포인터가 있습니다.

  • 원시 포인터 :
    • 원시 포인터 [즉 SomeClass* ptrToSomeClass = new SomeClass();]
  • 스마트 포인터 :
    • 고유 포인터 [즉
      std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
      ]
    • 공유 포인터 [ie
      std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
      ]
    • 약한 포인터 [ie
      std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
      ]

원시 포인터 (때때로 "레거시 포인터"또는 "C 포인터"라고 함)는 '베어 본'포인터 동작을 제공하며 버그 및 메모리 누수의 일반적인 원인입니다. 원시 포인터는 리소스의 소유권을 추적하는 수단을 제공하지 않으며 개발자는 메모리 누수가 발생하지 않도록 수동으로 '삭제'를 호출해야합니다. 어떤 객체가 여전히 리소스를 가리키고 있는지 알기가 어려울 수 있으므로 리소스가 공유되면 어려워집니다. 이러한 이유로 원시 포인터는 일반적으로 사용하지 말아야하며 범위가 제한된 코드의 성능에 중요한 섹션에서만 사용해야합니다.

고유 포인터는 자원에 대한 기본 원시 포인터를 '소유'하는 기본 스마트 포인터이며 고유 포인터를 '소유'하는 오브젝트가 범위를 벗어나면 delete를 호출하고 할당 된 메모리를 해제합니다. 'unique'라는 이름은 특정 시점에서 하나의 객체 만 고유 한 포인터를 '소유'할 수 있다는 사실을 나타냅니다. 이동 명령을 통해 소유권을 다른 객체로 이전 할 수 있지만 고유 한 포인터는 절대 복사하거나 공유 할 수 없습니다. 이러한 이유로 고유 포인터는 주어진 시간에 하나의 객체 만 포인터를 필요로하는 경우 원시 포인터를 대체 할 수있는 좋은 방법이며, 이는 소유 객체의 수명주기가 끝날 때 개발자가 메모리를 확보 할 필요가 없도록합니다.

공유 포인터는 고유 포인터와 유사한 또 다른 유형의 스마트 포인터이지만 많은 객체가 공유 포인터를 소유 할 수 있습니다. 고유 포인터와 마찬가지로 공유 포인터는 모든 객체가 리소스를 가리키면 할당 된 메모리를 해제합니다. 이를 참조 카운트라고하는 기술로 수행합니다. 새 객체가 공유 포인터의 소유권을 가질 때마다 참조 횟수가 1 씩 증가합니다. 마찬가지로 개체가 범위를 벗어나거나 리소스를 가리 키지 않으면 참조 횟수가 1 씩 감소합니다. 기준 카운트가 0에 도달하면 할당 된 메모리가 해제됩니다. 이러한 이유로 공유 포인터는 여러 개체가 동일한 리소스를 가리켜 야 할 때마다 사용해야하는 매우 강력한 유형의 스마트 포인터입니다.

마지막으로 약한 포인터는 리소스를 직접 가리 키지 않고 다른 포인터 (약하거나 공유 된)를 가리키는 또 다른 유형의 스마트 포인터입니다. 약한 포인터는 객체에 직접 액세스 할 수 없지만 객체가 여전히 존재하는지 또는 만료되었는지를 알 수 있습니다. 약한 포인터는 공유 포인터로 일시적으로 변환되어 지정된 객체에 액세스 할 수 있습니다 (아직 존재하는 경우). 설명하기 위해 다음 예제를 고려하십시오.

  • 바쁘고 겹치는 회의 : 회의 A 및 회의 B
  • 회의 A로 가고 동료가 회의 B로갑니다.
  • 동료에게 회의 A가 끝난 후에도 회의 B가 계속 진행되면 회의에 참석할 것이라고 말합니다.
  • 다음 두 가지 시나리오가 가능합니다.
    • 회의 A가 종료되고 회의 B가 계속 진행 중이므로 참여
    • 회의 A가 종료되고 회의 B도 종료되었으므로 참가할 수 없습니다

예를 들어, 미팅 B에 대한 약한 포인터가 있습니다. 미팅 B의 "소유자"가 아니므로 미팅없이 종료 할 수 있으며 확인하지 않으면 종료 여부를 알 수 없습니다. 종료되지 않은 경우 가입 및 참여할 수 있습니다. 그렇지 않으면 참여할 수 없습니다. 이는 미팅 A와 미팅 B 모두에서 동시에 "참가자"가되므로 미팅 B에 대한 공유 포인터를 갖는 것과 다릅니다.

이 예제는 약한 포인터의 작동 방식을 보여 주며 객체가 외부 관찰자 여야 하지만 소유권 공유 책임을 원하지 않는 경우에 유용합니다 . 이것은 두 객체가 서로를 가리켜 야하는 시나리오 (일명 순환 참조)에서 특히 유용합니다. 공유 포인터를 사용하면 다른 객체가 여전히 '강하게'가리 키기 때문에 어느 객체도 해제 할 수 없습니다. 포인터 중 하나가 약한 포인터 인 경우, 약한 포인터를 보유한 객체는 필요할 때 다른 객체에 여전히 액세스 할 수 있습니다 (아직 존재하는 경우).


6

이미 언급 된 다른 유효한 사용 사례 외에도 std::weak_ptr멀티 스레드 환경에서 멋진 도구는 다음과 같습니다.

  • 객체를 소유하지 않으므로 다른 스레드에서 삭제를 방해 할 수 없습니다
  • std::shared_ptr결합과는 std::weak_ptr반대측에 - 포인터 매달려 대해 안전 std::unique_ptr원시 포인터와 함께
  • std::weak_ptr::lock()원자 연산입니다 ( weak_ptr의 스레드 안전성 정보 참조 ).

디렉토리의 모든 이미지 (~ 10.000)를 동시에 메모리 (예 : 썸네일 캐시)에로드하는 작업을 고려하십시오. 이 작업을 수행하는 가장 좋은 방법은 이미지를 처리하고 관리하는 제어 스레드와 이미지를로드하는 여러 작업자 스레드입니다. 이제 이것은 쉬운 작업입니다. 여기에 매우 단순화 된 구현이 있습니다 (예 join(): 생략, 스레드는 실제 구현 등에서 다르게 처리되어야 함)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

그러나 이미지로드를 중단하려면 사용자가 다른 디렉토리를 선택했기 때문에 훨씬 더 복잡해집니다. 또는 관리자를 파괴하려는 경우에도 마찬가지입니다.

스레드 통신이 필요하고 모든 로더 스레드를 중지해야합니다. m_imageDatas필드를 합니다. 그렇지 않으면 이미 사용되지 않은 이미지라도 모든 이미지가 완성 될 때까지 로더는 로딩을 계속합니다. 단순화 된 예에서는 그렇게 어렵지는 않지만 실제 환경에서는 상황이 훨씬 더 복잡 할 수 있습니다.

스레드는 여러 관리자가 사용하는 스레드 풀의 일부일 수 있으며, 일부 관리자는 중지되고 일부는 그렇지 않습니다. 간단한 매개 변수 imagesToLoad는 잠긴 큐이며 해당 관리자는 다른 제어 스레드에서 이미지 요청을 푸시합니다. 독자들이 다른 쪽 끝에 임의의 순서로 요청을 표시합니다. 따라서 통신이 어렵고 느리고 오류가 발생하기 쉽습니다. 이러한 경우 추가 통신을 피하는 매우 우아한 방법 std::shared_ptr은와 함께 사용 하는 것입니다 std::weak_ptr.

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

이 구현은 첫 번째 구현과 거의 비슷하며 추가 스레드 통신이 필요하지 않으며 실제 구현에서 스레드 풀 / 큐의 일부일 수 있습니다. 만료 된 이미지를 건너 뛰고 만료되지 않은 이미지를 처리하므로 정상적인 작동 중에 스레드를 중지 할 필요가 없습니다. 소유 포인터가 만료되지 않은 경우 리더 fn이 확인하므로 항상 경로를 안전하게 변경하거나 관리자를 파괴 할 수 있습니다.


2

http://en.cppreference.com/w/cpp/memory/weak_ptr std :: weak_ptr은 std :: shared_ptr에서 관리하는 객체에 대한 비 소유 ( "약한") 참조를 보유하는 스마트 포인터입니다. 참조 된 오브젝트에 액세스하려면 std :: shared_ptr로 변환해야합니다.

std :: weak_ptr은 임시 소유권을 모델링합니다. 개체가 존재하는 경우에만 개체에 액세스해야하고 다른 사람이 언제든지 삭제할 수있는 경우 std :: weak_ptr은 개체를 추적하는 데 사용되며 std로 변환됩니다. : shared_ptr은 임시 소유권을 가정합니다. 이때 원래 std :: shared_ptr이 소멸되면 임시 std :: shared_ptr도 소멸 될 때까지 오브젝트의 수명이 연장됩니다.

또한 std :: weak_ptr은 std :: shared_ptr의 순환 참조를 분리하는 데 사용됩니다.


" 순환 참조를 끊는 방법"어떻게?
curiousguy

2

공유 포인터의 단점이 있습니다. shared_pointer는 부모-자식주기 종속성을 처리 할 수 ​​없습니다. 자식 클래스가 부모 클래스의 개체를 사용하는 경우 동일한 파일에서 부모 클래스가 공유 포인터를 사용하여 자식 클래스의 개체를 사용 하는지를 의미합니다. 공유 포인터는 사이클 종속성 시나리오에서 소멸자를 호출하지 않는 경우에도 모든 객체를 파괴하지 못합니다. 기본적으로 공유 포인터는 참조 횟수 메커니즘을 지원하지 않습니다.

이 단점은 weak_pointer를 사용하여 극복 할 수 있습니다.


약한 참조가 순환 종속성을 어떻게 다룰 수 있습니까?
curiousguy

1
@ curiousguy, 자식은 부모에 대한 약한 참조를 사용하고, 공유하는 (강한) 참조가 없을 때 부모를 할당 해제 할 수 있습니다. 따라서 자식을 통해 부모에 액세스 할 때 약한 참조는 부모가 여전히 사용 가능한지 테스트해야합니다. 또는 추가 조건을 피하기 위해 순환 참조 추적 메커니즘 (마크 스윕 또는 참조 횟수 감소에 대한 프로빙으로 둘 다 점근 성능이 좋지 않은 경우)은 부모 및 자식에 대한 유일한 공유 참조가 각각에서 온 경우 순환 공유 참조를 손상시킬 수 있습니다. 다른.
쉘비 무어 III

@ShelbyMooreIII " 는 부모가 여전히 사용 가능한지 확인하기 위해 테스트를해야합니다. "예, 사용할 수없는 사례에 올바르게 대응할 수 있어야합니다! 실제 (즉, 강한) 심판에서는 발생하지 않습니다. 약한 심판은 대체품이 아니라 논리를 변경해야한다는 것을 의미합니다.
curiousguy

2
@ curiousguy 당신은 묻지 않았다 "어떻게 weak_ptr대체 대체로 프로그램 논리의 변경없이 순환 종속성을 처리 할 수 shared_ptr있습니까?" :-)
쉘비 무어 III

2

객체를 소유하고 싶지 않은 경우 :

전의:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

위의 클래스에서 wPtr1은 wPtr1이 가리키는 자원을 소유하지 않습니다. 자원이 삭제되면 wPtr1이 만료됩니다.

순환 종속성을 피하려면

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

이제 클래스 B와 A의 shared_ptr을 만들면 두 포인터의 use_count는 2입니다.

shared_ptr이 od 범위를 벗어나도 계수는 여전히 1로 유지되므로 A 및 B 오브젝트는 삭제되지 않습니다.

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

산출:

A()
B()

출력에서 볼 수 있듯이 A 및 B 포인터는 절대 삭제되지 않으므로 메모리 누수가 발생합니다.

이러한 문제를 피하려면 클래스 A에서 shared_ptr 대신 weak_ptr을 사용하면됩니다.


2

내가 볼 std::weak_ptr<T>A와 손잡이 A와 std::shared_ptr<T>그것은 저를 얻을 수 있습니다 : std::shared_ptr<T>그것은 여전히 존재하는 경우, 그러나 그것의 수명을 연장하지 않습니다. 이러한 관점이 유용한 경우 몇 가지 시나리오가 있습니다.

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

또 다른 중요한 시나리오는 데이터 구조의주기를 중단하는 것입니다.

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

Herb Sutter는 언어 기능 (이 경우 스마트 포인터)을 최대한 사용하는 방법을 설명 하는 훌륭한 강연 을합니다. 기본적으로 누출 자유 . 반드시 봐야합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.