C++ Chapter 15.7 : 스마트 포인터3️⃣ std::weak_ptr

Date:     Updated:

카테고리:

태그:

인프런에 있는 홍정모 교수님의 홍정모의 따라 하며 배우는 C++ 강의를 듣고 정리한 필기입니다. 😀
🌜 [홍정모의 따라 하며 배우는 C++]강의 들으러 가기!


chapter 15. 의미론적 이동과 스마트 포인터

15.7 스마트 포인터3️⃣ std::weak_ptr

#include <memory>

🚀 참고

image

image

image

image

image

image

내가 쓴 답변이다. 이 블로그 이 이 답변을 작성하는데에 도움 되었다. std::shared_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;
	}
};
  • partnerUp 함수
    • Person 객체를 소유하는 인수로 들어온 shared_ptr 스마트 포인터를 참조하는 p1p2
      • 서로를 서로의 파트너로 지정하는 역할을 하는 함수다.
        • p1의 m_partner 을 p2로 설정
        • p2의 m_partner 을 p1로 설정
        • m_partnershared_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 둘 다 가지고 있게 됨
  • 블록 밖을 벗어나거나 프로그램 종료시
    • 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_partnerweak_ptr이므로 카운트에 세지 않기 때문에 2 개가 되는 것이 아닌 1 개로 유지하게 된다. 그렇기 때문에 나중에 블록 밖을 벗어날시 0 개가 되어 성공적으로 두 객체가 delete 될 수 있게 된 것이다.
        • 👉 약한 참조
  • partnerUp 함수의 결과로
    • lucy는 “Lucy” 객체를 강하게 참조하지만 “Ricky”는 약하게 참조한다. (👉 공유하는 객체로 카운트 되진 않는다.)
    • ricky는 “Ricky” 객체를 강하게 참조하지만 “Lucy”는 약하게 참조한다. (👉 공유하는 객체로 카운트 되진 않는다.)


주의할 점

weak_ptr 자체로는 소유하고 있는 객체의 멤버나 포인터에 접근할 수 없어서 그러기 위해선 반드시 shared_ptr로 변환해서 사용하여야 한다. 👈 lock 함수를 통하여 실현

weak_ptr은 오직 shared_ptr이나 weak_ptr만 대입 및 할당 받을 수 있다. weak_ptr엔 직접적인 데이터의 주소를 대입할 수 없다. 복사 생성자, 대입 연산자 오버로딩도 shared_ptr이나 weak_ptr만 파라미터로 받게끔 내부적으로 구현이 되어 있음. 그리고 직접적인 데이터는 lock 함수 shared_ptr을 리턴 받아서 또 이를 통해 두 다리 건너 접근해야 한다.

weak_ptr<Person> m_partner = 

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_ptrm_partnerlock()함수 호출
    • lucy가 소유하는 객체의 멤버인 m_partner는 현재 소유하고 있는 객체 ricky가 있다.
      • 따라서 lock()함수는 이를 shared_ptr로서 임시 변환되어 리턴한다.
        • weak_ptr로는 소유하고 있는 객체의 멤버에 접근하는 것이 불가능 한데 lucy->getPartner() 이렇게 shared_ptr로서 리턴되어 getName() 멤버 함수에 접근할 수 있게 되었다.
  • 포인터로 접근하려면 shared_ptr로 변환된 후 get() 함수를 거쳐야 한다.


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

Cpp 카테고리 내 다른 글 보러가기

댓글 남기기