답변:
좋은 예는 캐시입니다.
최근에 액세스 한 객체의 경우 메모리에 유지하려는 경우 객체에 대한 강력한 포인터를 갖습니다. 주기적으로 캐시를 스캔하고 최근에 액세스하지 않은 개체를 결정합니다. 메모리에 보관할 필요가 없으므로 강력한 포인터를 제거하십시오.
그러나 그 객체가 사용 중이고 다른 코드가 그것에 대한 강력한 포인터를 가지고 있다면 어떨까요? 캐시가 객체에 대한 유일한 포인터를 제거하면 다시는 찾을 수 없습니다. 따라서 캐시는 메모리에 남아 있는지 확인해야하는 객체에 대한 약한 포인터를 유지합니다.
이것은 약한 포인터가하는 일입니다. 여전히 물체가 있으면 물체를 찾을 수 있지만 다른 물체가 필요하지 않으면 주위를 유지하지 않습니다.
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";
}
std::weak_ptr::lock
std::shared_ptr
관리 대상 개체의 소유권을 공유 하는 새 항목 을 만듭니다 .
또 다른 대답, 희망적으로 더 간단합니다. (동료 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
shared_ptr
소유권을 공유하는 것이므로 아무도 메모리를 해제 할 특별한 책임이 없으므로 더 이상 사용하지 않으면 자동으로 해제됩니다. 루프가없는 한 ... 여러 팀이 같은 플레이어를 공유 할 수 있습니다 (과거 팀?). 팀 오브젝트가 멤버를 "소유"하는 경우, shared_ptr
시작 하기 위해 a 를 사용할 필요가 없습니다 .
shared_ptr
"팀원"에 의해 참조되기 때문에 언제 파괴됩니까? 당신이 설명하는 것은 루프가없는 경우입니다.
다음은 @jleahy가 제공 한 한 가지 예입니다. 작업 모음이 있고 비동기 적으로 실행되고에 의해 관리된다고 가정합니다 std::shared_ptr<Task>
. 이러한 작업으로 주기적으로 작업을 수행 할 수 있으므로 타이머 이벤트가 통과 std::vector<std::weak_ptr<Task>>
하여 작업에 수행 할 작업을 제공 할 수 있습니다. 그러나 동시에 작업이 더 이상 필요없고 죽었다고 동시에 결정했을 수 있습니다. 따라서 타이머는 약한 포인터에서 공유 포인터를 만들고 공유 포인터가 null이 아닌 경우 해당 공유 포인터를 사용하여 작업이 아직 살아 있는지 확인할 수 있습니다.
비동기 핸들러가 호출 될 때 대상 오브젝트가 여전히 존재한다고 보장 할 수없는 경우 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()
보류중인 비동기 처리기가 대상 객체의 수명을 연장 하지는 않지만 대상 객체가 삭제 된 경우에도 여전히 안전합니다.
this
self = shared_from_this()
핸들러가 동일한 클래스 내에서 메소드를 호출 할 때 관용구를 사용할 때 습관이 발생 합니다.
포인터를 사용할 때는 사용 가능한 여러 유형의 포인터와 각 포인터를 사용하는 것이 합리적 일 때를 이해하는 것이 중요합니다. 다음과 같이 두 가지 범주에 네 가지 유형의 포인터가 있습니다.
SomeClass* ptrToSomeClass = new SomeClass();
]std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() );
std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() );
std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr );
원시 포인터 (때때로 "레거시 포인터"또는 "C 포인터"라고 함)는 '베어 본'포인터 동작을 제공하며 버그 및 메모리 누수의 일반적인 원인입니다. 원시 포인터는 리소스의 소유권을 추적하는 수단을 제공하지 않으며 개발자는 메모리 누수가 발생하지 않도록 수동으로 '삭제'를 호출해야합니다. 어떤 객체가 여전히 리소스를 가리키고 있는지 알기가 어려울 수 있으므로 리소스가 공유되면 어려워집니다. 이러한 이유로 원시 포인터는 일반적으로 사용하지 말아야하며 범위가 제한된 코드의 성능에 중요한 섹션에서만 사용해야합니다.
고유 포인터는 자원에 대한 기본 원시 포인터를 '소유'하는 기본 스마트 포인터이며 고유 포인터를 '소유'하는 오브젝트가 범위를 벗어나면 delete를 호출하고 할당 된 메모리를 해제합니다. 'unique'라는 이름은 특정 시점에서 하나의 객체 만 고유 한 포인터를 '소유'할 수 있다는 사실을 나타냅니다. 이동 명령을 통해 소유권을 다른 객체로 이전 할 수 있지만 고유 한 포인터는 절대 복사하거나 공유 할 수 없습니다. 이러한 이유로 고유 포인터는 주어진 시간에 하나의 객체 만 포인터를 필요로하는 경우 원시 포인터를 대체 할 수있는 좋은 방법이며, 이는 소유 객체의 수명주기가 끝날 때 개발자가 메모리를 확보 할 필요가 없도록합니다.
공유 포인터는 고유 포인터와 유사한 또 다른 유형의 스마트 포인터이지만 많은 객체가 공유 포인터를 소유 할 수 있습니다. 고유 포인터와 마찬가지로 공유 포인터는 모든 객체가 리소스를 가리키면 할당 된 메모리를 해제합니다. 이를 참조 카운트라고하는 기술로 수행합니다. 새 객체가 공유 포인터의 소유권을 가질 때마다 참조 횟수가 1 씩 증가합니다. 마찬가지로 개체가 범위를 벗어나거나 리소스를 가리 키지 않으면 참조 횟수가 1 씩 감소합니다. 기준 카운트가 0에 도달하면 할당 된 메모리가 해제됩니다. 이러한 이유로 공유 포인터는 여러 개체가 동일한 리소스를 가리켜 야 할 때마다 사용해야하는 매우 강력한 유형의 스마트 포인터입니다.
마지막으로 약한 포인터는 리소스를 직접 가리 키지 않고 다른 포인터 (약하거나 공유 된)를 가리키는 또 다른 유형의 스마트 포인터입니다. 약한 포인터는 객체에 직접 액세스 할 수 없지만 객체가 여전히 존재하는지 또는 만료되었는지를 알 수 있습니다. 약한 포인터는 공유 포인터로 일시적으로 변환되어 지정된 객체에 액세스 할 수 있습니다 (아직 존재하는 경우). 설명하기 위해 다음 예제를 고려하십시오.
예를 들어, 미팅 B에 대한 약한 포인터가 있습니다. 미팅 B의 "소유자"가 아니므로 미팅없이 종료 할 수 있으며 확인하지 않으면 종료 여부를 알 수 없습니다. 종료되지 않은 경우 가입 및 참여할 수 있습니다. 그렇지 않으면 참여할 수 없습니다. 이는 미팅 A와 미팅 B 모두에서 동시에 "참가자"가되므로 미팅 B에 대한 공유 포인터를 갖는 것과 다릅니다.
이 예제는 약한 포인터의 작동 방식을 보여 주며 객체가 외부 관찰자 여야 하지만 소유권 공유 책임을 원하지 않는 경우에 유용합니다 . 이것은 두 객체가 서로를 가리켜 야하는 시나리오 (일명 순환 참조)에서 특히 유용합니다. 공유 포인터를 사용하면 다른 객체가 여전히 '강하게'가리 키기 때문에 어느 객체도 해제 할 수 없습니다. 포인터 중 하나가 약한 포인터 인 경우, 약한 포인터를 보유한 객체는 필요할 때 다른 객체에 여전히 액세스 할 수 있습니다 (아직 존재하는 경우).
이미 언급 된 다른 유효한 사용 사례 외에도 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이 확인하므로 항상 경로를 안전하게 변경하거나 관리자를 파괴 할 수 있습니다.
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의 순환 참조를 분리하는 데 사용됩니다.
공유 포인터의 단점이 있습니다. shared_pointer는 부모-자식주기 종속성을 처리 할 수 없습니다. 자식 클래스가 부모 클래스의 개체를 사용하는 경우 동일한 파일에서 부모 클래스가 공유 포인터를 사용하여 자식 클래스의 개체를 사용 하는지를 의미합니다. 공유 포인터는 사이클 종속성 시나리오에서 소멸자를 호출하지 않는 경우에도 모든 객체를 파괴하지 못합니다. 기본적으로 공유 포인터는 참조 횟수 메커니즘을 지원하지 않습니다.
이 단점은 weak_pointer를 사용하여 극복 할 수 있습니다.
weak_ptr
대체 대체로 프로그램 논리의 변경없이 순환 종속성을 처리 할 수 shared_ptr
있습니까?" :-)
객체를 소유하고 싶지 않은 경우 :
전의:
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을 사용하면됩니다.
내가 볼 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는 언어 기능 (이 경우 스마트 포인터)을 최대한 사용하는 방법을 설명 하는 훌륭한 강연 을합니다. 기본적으로 누출 자유 . 반드시 봐야합니다.