C++ Chapter 15.7 : 스마트 포인터3️⃣ std::weak_ptr
카테고리: Cpp
태그: Cpp Programming
인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!
chapter 15. 의미론적 이동과 스마트 포인터
15.7 스마트 포인터3️⃣ std::weak_ptr
#include <memory>
🔔 shared_ptr의 순환 의존성 문제
#include <iostream>
#include <memory>
#include <string>
class Person
{
std::string m_name;
std::shared_ptr<Person> m_partner;
//std::weak_ptr<Person> m_partner;
public:
Person(const std::string &name) : m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp (std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2) // ⭐⭐
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is partnered with " << p2->m_name << "\n";
return true;
}
const std::string & getName() const
{
return m_name;
}
};
- partnerUp 함수
- Person 객체를 소유하는 인수로 들어온 shared_ptr 스마트 포인터를 참조하는
p1
과p2
- 서로를 서로의 파트너로 지정하는 역할을 하는 함수다.
p1
의 m_partner 을p2
로 설정p2
의 m_partner 을p1
로 설정- m_partner는
shared_ptr
인 멤버다.
- 서로를 서로의 파트너로 지정하는 역할을 하는 함수다.
- Person 객체를 소유하는 인수로 들어온 shared_ptr 스마트 포인터를 참조하는
순환 참조성 문제 👉 shared_ptr로 서로가 서로를 참조할시 소유권이 순환이 되서 영원히 두 객체를 delete 할 수 없는 문제.
- 서로가 서로를 가리키는
shared_ptr
을 가지고 있다면use_count()
값은 절대 0 이 되지 않으므로 메모리는 영원히 해제 되지 않는 문제가 발생 한다.
int main()
{
auto lucy = std::make_shared<Person>("Lucy");
auto ricky = std::make_shared<Person>("Ricky");
return 0;
}
💎출력💎
Lucy created
Ricky created
Ricky destroyed
Lucy destroyed
- 위와 같이 partnerUp 함수를 실행하지 않았을 때는, 즉 두 shared_ptr
lucy
,ricky
가 서로가 서로를 참조하고 있지 않을 때는 둘 다 블록 밖을 벗어날 때 정상적으로 delete 된 것을 볼 수 있다.
int main()
{
auto lucy = std::make_shared<Person>("Lucy");
auto ricky = std::make_shared<Person>("Ricky");
partnerUp(lucy, ricky); // ⭐⭐
return 0;
}
💎출력💎
Lucy created
Ricky created
Lucy is partnered with Ricky
- 두 소멸자가 호출 되지 않은 것으로 보아 두 Person 객체가 delete 되지 않은 것을 알 수 있다.
- partnerUp 함수로 인하여 서로를 참조하게 된 것이 문제.
- “Lucy” 객체에 대한 소유권은
lucy
,ricky
둘 다 가지고 있게 됨 - “Ricky” 객체에 대한 소유권은
lucy
,ricky
둘 다 가지고 있게 됨
- “Lucy” 객체에 대한 소유권은
- partnerUp 함수로 인하여 서로를 참조하게 된 것이 문제.
- 블록 밖을 벗어나거나 프로그램 종료시
auto lucy = std::make_shared<Person>("Lucy")
가 소멸 되려고 할 때- 👉 “Lucy”에 대한 소유권이 ricky에게도 있으므로 아직 소유하고 있는 포인터가 남아 있으므로 “Lucy” 객체는 delete 되지 않음.
auto ricky = std::make_shared<Person>("Ricky")
가 소멸 되려고 할 때.- 👉 “Ricky”에 대한 소유권이 lucy에게도 있으므로 아직 소유하고 있는 포인터가 남아 있으므로 “Ricky” 객체는 delete 되지 않음.
- 이렇게 영원히 참조 개수가 0 이 될 수 없어서 이러지도 저러지도 못하는 상황이 된다.
🔔 std::weak_ptr
shared_ptr의 순환 의존성 문제를 해결해준다.
#include <iostream>
#include <memory>
#include <string>
class Person
{
std::string m_name;
//std::shared_ptr<Person> m_partner;
std::weak_ptr<Person> m_partner;
public:
Person(const std::string &name) : m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is partnered with " << p2->m_name << "\n";
return true;
}
const std::string & getName() const
{
return m_name;
}
};
int main()
{
auto lucy = std::make_shared<Person>("Lucy");
auto ricky = std::make_shared<Person>("Ricky");
partnerUp(lucy, ricky);
return 0;
}
💎출력💎
Lucy created
Ricky created
Lucy is partnered with Ricky
Ricky destroyed
Lucy destroyed
- m_partner를 shared_ptr 에서
weak_ptr
로 바꿔주니 순환 참조 문제가 제거 되었다. 두 객체가 잘 delete 된 것을 알 수 있다.std::weak_ptr<Person> m_partner;
weak_ptr
- 하나 이상의 shared_ptr 스마트 포인터가 소유하는 객체에 대한 접근을 제공하지만 소유자의 수에는 포함되지 않는다. 레퍼런스 카운트에 포함되지 않는다.
- 일반 포인터와 shared_ptr 사이에 위치한 스마트 포인터이다.
- 스마트 포인터처럼 자동으로 delete도 해주고 객체를 안전하게 참조할 수 있게 해주지만
- shared_ptr과 달리 참조 개수를 늘리지 않는다.
- 따라서 위의 예제에서 m_partner는
weak_ptr
이므로 카운트에 세지 않기 때문에 2 개가 되는 것이 아닌 1 개로 유지하게 된다. 그렇기 때문에 나중에 블록 밖을 벗어날시 0 개가 되어 성공적으로 두 객체가 delete 될 수 있게 된 것이다. - 👉 약한 참조
- 따라서 위의 예제에서 m_partner는
- partnerUp 함수의 결과로
lucy
는 “Lucy” 객체를 강하게 참조하지만 “Ricky”는 약하게 참조한다. (👉 공유하는 객체로 카운트 되진 않는다.)ricky
는 “Ricky” 객체를 강하게 참조하지만 “Lucy”는 약하게 참조한다. (👉 공유하는 객체로 카운트 되진 않는다.)
주의할 점
weak_ptr
자체로는 소유하고 있는 객체의 멤버나 포인터에 접근할 수 없어서 그러기 위해선 반드시shared_ptr
로 변환해서 사용하여야 한다. 👈 lock 함수를 통하여 실현
lock 함수
weak_ptr
자체 안에서 정의가 되어 있다.weak_ptr
이 가리키는 객체가 아직 메모리에 살아있다면- 해당 객체를 가리키는
shared_ptr
을 리턴한다.
- 해당 객체를 가리키는
weak_ptr
이 가리키는 객체가 이미 메모리에서 해제되었다면- 아무것도 가리키지 않는
shared_ptr
을 리턴한다.
- 아무것도 가리키지 않는
#include <iostream>
#include <memory>
#include <string>
class Person
{
std::string m_name;
//std::shared_ptr<Person> m_partner;
std::weak_ptr<Person> m_partner; // ⭐⭐⭐
public:
Person(const std::string &name) : m_name(name)
{
std::cout << m_name << " created\n";
}
~Person()
{
std::cout << m_name << " destroyed\n";
}
friend bool partnerUp(std::shared_ptr<Person> &p1, std::shared_ptr<Person> &p2)
{
if (!p1 || !p2)
return false;
p1->m_partner = p2;
p2->m_partner = p1;
std::cout << p1->m_name << " is partnered with " << p2->m_name << "\n";
return true;
}
const std::shared_ptr<Person> getPartner() const // ⭐⭐⭐
{
return m_partner.lock(); // ⭐⭐ lock 함수 실행
}
const std::string & getName() const
{
return m_name;
}
};
int main()
{
auto lucy = std::make_shared<Person>("Lucy");
auto ricky = std::make_shared<Person>("Ricky");
partnerUp(lucy, ricky);
return 0;
}
int main()
{
auto lucy = std::make_shared<Person>("Lucy");
auto ricky = std::make_shared<Person>("Ricky");
partnerUp(lucy, ricky);
std::cout << lucy->getPartner()->getName() << std::endl; // Ricky 출력
return 0;
}
💎출력💎
Lucy created
Ricky created
Lucy is partnered with Ricky
Ricky
Ricky destroyed
Lucy destroyed
lucy->getPartner()->getName()
- getPartner함수의 return m_partner.
lock()
weak_ptr
인 m_partner의lock()
함수 호출
lucy
가 소유하는 객체의 멤버인m_partner
는 현재 소유하고 있는 객체ricky
가 있다.- 따라서
lock()
함수는 이를shared_ptr
로서 임시 변환되어 리턴한다.- ✨
weak_ptr
로는 소유하고 있는 객체의 멤버에 접근하는 것이 불가능 한데lucy->getPartner()
이렇게 shared_ptr로서 리턴되어getName()
멤버 함수에 접근할 수 있게 되었다.
- ✨
- 따라서
- getPartner함수의 return m_partner.
- 포인터로 접근하려면 shared_ptr로 변환된 후
get()
함수를 거쳐야 한다.
🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄
댓글남기기