weak_ptr
메모리 누수를 방지하기 위해서 우리는 스마트 포인터를 사용하려 한다. 하지만 클래스의 멤버 변수에서 shared_ptr을 사용할 경우 순환 참조가 발생할 경우 메모리 누수가 발생한다. 이를 방지하기 위해서 weak_ptr을 사용할 수 있다.
weak_ptr을 사용하면 shared_ptr처럼 오브젝트를 가리킬 수 있지만 Reference count에는 아무런 영향을 주지 않는다. 아래의 예시를 살펴보자. weak_ptr이 객체를 가리키고 있지만 shared_ptr이 해제될 때 함께 해제되는 모습을 확인할 수 있다. 또한 weak_ptr이 객체를 참조하고 있지만 참조 카운터는 증가하지 않은 모습도 확인할 수 있다.
class A {
public:
A(int n) : num(n) { std::cout << "A() | num: " << num << std::endl; }
int GetNum() const { return num; }
~A() { std::cout << "~A() | num: " << num << std::endl; }
private:
int num;
};
int main()
{
std::weak_ptr<A> wPtr;
std::cout << "Begin scope" << std::endl;
{
std::shared_ptr<A> sPtr = std::make_shared<A>(10);
wPtr = sPtr;
std::cout << "count: "<< sPtr.use_count() << std::endl;
}
std::cout << "End scope" << std::endl;
return 0;
}
출력 결과
Begin scope
A() | num: 10
count: 1
~A() | num: 10
End scope
weak_ptr을 shared_ptr로 전환
weak_ptr을 사용하기 위해서는 shared_ptr로 전환을 한 후 사용해야 한다. 이때 멤버함수 lock
을 사용하면 전환 성공시 shared_ptr을 반환하고, 전환 실패 시 nullptr
을 반환한다. 아래의 예시에서 shared_ptr을 반환할 경우 참조 카운터가 증가함을 확인할 수 있다.
class A {
public:
A(int n) : num(n) { std::cout << "A() | num: " << num << std::endl; }
int GetNum() const { return num; }
~A() { std::cout << "~A() | num: " << num << std::endl; }
private:
int num;
};
int main()
{
std::weak_ptr<A> wPtr;
std::cout << "Begin scope" << std::endl;
{
std::shared_ptr<A> sPtr = std::make_shared<A>(10);
wPtr = sPtr;
std::cout << "count: "<< sPtr.use_count() << std::endl;
if (const auto spt = wPtr.lock())
{
std::cout << "Successful creation of shared_ptr!" << std::endl;
std::cout << "count: " << sPtr.use_count() << std::endl;
}
else
{
std::cout << "Failed to create shared_ptr" << std::endl;
std::cout << "count: " << sPtr.use_count() << std::endl;
}
}
std::cout << "End scope" << std::endl;
if (const auto spt = wPtr.lock())
{
std::cout << "Successful creation of shared_ptr!" << std::endl;
}
else
{
std::cout << "Failed to create shared_ptr" << std::endl;
}
return 0;
}
출력 결과
Begin scope
A() | num: 10
count: 1
Successful creation of shared_ptr!
count: 2
~A() | num: 10
End scope
Failed to create shared_ptr
유효성 검사
weak_ptr에서 lock
멤버함수를 사용하면 반환 값으로 성공 시 shared_ptr을 실패 시 nulltpr
을 반환한다. weak_ptr에서 객체에 접근하기 위해서는 이러한 유효성 검사를 거쳐야 한다. 다른 방법으로는 expired
멤버함수를 활용할 수 있다. expired
멤버함수는 객체가 만료되었다면 true
를 반환하고 객체가 만료되지 접근 가능하다면 false
를 반환한다.
int main()
{
std::weak_ptr<A> wPtr;
std::cout << "Begin scope" << std::endl;
{
std::shared_ptr<A> sPtr = std::make_shared<A>(10);
wPtr = sPtr;
std::cout << "count: "<< sPtr.use_count() << std::endl;
if (wPtr.expired())
{
std::cout << "wPtr isn't valid" << std::endl;
}
else
{
std::cout << "wPtr is valid" << std::endl;
}
}
std::cout << "End scope" << std::endl;
if (wPtr.expired())
{
std::cout << "wPtr isn't valid" << std::endl;
}
else
{
std::cout << "wPtr is valid" << std::endl;
}
return 0;
}
출력 결과
Begin scope
A() | num: 10
count: 1
wPtr is valid
~A() | num: 10
End scope
wPtr isn't valid
순환참조 방지
클래스에서 멤버 변수로 shared_ptr을 가지게 되면 순환 참조로 인한 메모리 누수가 발생할 수 있다고 했다. 하지만 아래와 같이 weak_ptr을 활용하게 되면, 그리고 weak_ptr로 객체에 접근할 때만 lock
을 통해 shared_ptr을 생성하게 되면, 메모리 누수를 방지 할 수 있게 된다.
#include <iostream>
#include <memory>
class Cat
{
public:
Cat()
{
std::cout << "Cat()" << std::endl;
}
~Cat()
{
std::cout << "~Cat()" << std::endl;
}
// std::shared_ptr<Cat> mFriend;
std::weak_ptr<Cat> mFriend; // shared_ptr 에서 weak_ptr로 수정
};
int main()
{
std::shared_ptr<Cat> Jo = std::make_shared<Cat>();
std::shared_ptr<Cat> Moo = std::make_shared<Cat>();
Jo->mFriend = Moo;
Moo->mFriend = Jo;
}
이상으로 스마트 포인터에 대한 정리를 마친다.
<이전 글 Smart Pointer의 이해 - ② shared_ptr의 개념과 활용
참조:
유튜브 채널 - 코드없는 프로그래밍: https://www.youtube.com/channel/UCHcG02L6TSS-StkSbqVy6Fg
길벗 출판사 - C++14 STL 철저 입문