C++ Chapter 15.6 : 스마트 포인터2️⃣ std::shared_ptr

Date:     Updated:

카테고리:

태그:

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


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

15.6 스마트 포인터2️⃣ std::shared_ptr

#include <memory>

🔔 std::shared_ptr

객체에 대한 소유권을 나눠 주고 공유 한다.

  • 소유한 객체를 자동으로 delete해주는 스마트 포인터다.
  • unique_ptr과 달리 ✨여러 shared_ptr이 한 객체에 대해 동시에 공유(shared)할 수 있도록 한다.
    • unique_ptr은 소유권을 엄격히 관리할 때 사용
    • shared_ptr은 소유권을 느슨하게 관리할 때 사용
      • 소유권을 여러 shared_ptr 가 동시에 가질 수 있기 때문에 그 중 한 포인터로 delete되더라도 그 객체는 소멸되지 않는다. 소유권만 박탈될 뿐이다. 그 객체를 소유한 포인터가 하나 밖에 없을 때만 delete된다.
  • 또한 shared_ptr자신이 가리키고 있는 객체가 몇 군데서 공유되고 있는지 내부적으로 항상 세고 있다.

소유권 분배가 제대로 이루어 졌을 때

#include <iostream>
#include <memory>

int main()
{
    Resource * res = new Resource(3);
	res->setAll(1);
	std::shared_ptr<Resource> ptr1(res);

    ptr1 -> print();

    {
		std::shared_ptr<Resource> ptr2(ptr1);

		ptr2->setAll(5);
		ptr2->print();

        std::cout << "Going out of the block" << std::endl;
	}

	ptr1->print();

    std::cout << "Going out of the block" << std::endl;

	return 0;
}
💎출력💎

Resource length constructed
1 1 1
5 5 5
Going out of the block
5 5 5
Going out of the block
Resource destroyed
  • Resource 클래스 코드는 이전 포스트에 있습니다.
  • image
    • 소유권 분배 👉 std::shared_ptr<Resource> ptr2(ptr1);
      std::shared_ptr<Resource> ptr1(res);
      std::shared_ptr<Resource> ptr2(ptr1);
      
      • 👉std::shared_ptr<Resource> ptr2(ptr1);
      • ptr1이 가리키고 있던 Resource 객체의 소유권을 ptr2에게도 분배한다.
      • ptr1ptr2이 동일한 객체를 가리키게 된다. 동시에 소유권을 가진다.
    • ptr2(ptr1) 👉 ⭐ptr1에게도 소유권이 있다는 것을 ptr2 가 알고 있다.
      • 따라서 ptr2이 블록 밖을 벗어나서 delete 되더라도 여전히 ptr1과 함께 소유했던 그 res 객체는 사라지지 않는다.
      • ptr1ptr2unique_ptr 이었으면 ptr2이 사라짐과 동시에 res객체도 delete 되었을 것이다.
      • 객체의 유일한 소유권자가 사라질 때 그제서야 delete 된다.

소유권 분배가 제대로 이루어지지 않았을 때

int main()
{
	Resource * res = new Resource(3);
	res->setAll(1);
	std::shared_ptr<Resource> ptr1(res);

	ptr1->print();

	{
		std::shared_ptr<Resource> ptr2(res); 

		ptr2->setAll(5);
		ptr2->print();

        std::cout << "Going out of the block" << std::endl;
	}

	ptr1->print();  // 📢 런타임 에러 발생!! 

    std::cout << "Going out of the block" << std::endl;

	return 0;
}
💎출력💎

Resource length constructed
1 1 1
5 5 5
Going out of the block
Resource destroyed
  • image
    • 소유권 분배가 제대로 이루어지지 않는다. 👉 std::shared_ptr<Resource> ptr2(res);
      std::shared_ptr<Resource> ptr1(res);
      std::shared_ptr<Resource> ptr2(res);
      
      • ptr1ptr2res라는 동일한 객체를 가리키고 소유하고 있지만 ⭐ptr1, ptr2둘 다 res에 대한 소유권이 있는 상태라는 것을 서로 알 수가 없다.
        • 따라서 ptr2이 블록 밖을 나오면서 delete 되면 res객체까지도 해제되기 때문에 ptr1->print();에서 런타임 에러가 발생한다. 없어진 객체에 접근하려고 하는 거니까!
std::shared_ptr<Resource> ptr1(res);
std::shared_ptr<Resource> ptr2(res);

auto ptr2 = ptr1;
  • shared_ptr 포인터 선언시 객체에 대해 정의할 것이라면
    • 👉 ptr2 = ptr1 이런 복사 방식을 통하여 ptr1, ptr2 둘 다 동일한 객체에 대한 소유권을 공유하고 있음을 서로에게 알려주어야 한다.


🔔 std::shared_ptr 관련 함수들

std::make_shared 함수

위와 같은 상황 때문에 make_shared 함수를 통하여 shared_ptr을 안전하게 생성할 수 있다.

std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>(3);
std::shared_ptr<Resource> ptr2 = ptr1 // ✨소유권을 공유하고 있음을 알려주어야 함.

// auto를 써주면 더 간단! 위와 같은 코드.
auto ptr1 = std::make_shared<Resource>(3);       
auto ptr2 = ptr1; 
  • Resource * res = new Resource(3); 과정이 필요 없다.
    • make_shared 함수를 통하여 객체 생성을 하기 때문에!
  • ptr1 = std::make_shared<Resource>(3);
    • 1️⃣ Resource 객체를 생성함과 동시에 2️⃣ 이를 소유하는 shared_ptr 스마트 포인터를 리턴한다.
  • make_shared 함수를 통해 객체 생성과 동시에 이를 소유하는 shared_ptr 스마트 포인터를 생성하는 것을 권장한다.


use_count(), reset() 함수

std::shared_ptr<Resource> ptr1 = std::make_shared<Resource>(3);
cout << "현재 소유자 수 : " << ptr1.use_count() << endl; // 1 출력

auto ptr2 = ptr1;
cout << "현재 소유자 수 : " << ptr1.use_count() << endl; // 2 출력

ptr2.reset();
cout << "현재 소유자 수 : " << ptr1.use_count() << endl; // 1 출력

use_count() : 어떤 객체를 소유하고 있는 shared_ptr 포인터들이 현재 총 몇 개인지 리턴해준다.

  • 여러 shared_ptr 포인터들이 하나의 객체를 나눠서 소유하고 있기 때문에 소유권을 가진 포인터가 아무것도 없을 때만, 즉 use_count() 결과가 0 일때만 객체가 delete 될 수 있도록 늘 소유권을 가진 shared_ptr 포인터가 총 몇개인지 세고 있다.

reset() : 해당 shared_ptr 포인터의 객체에 대한 소유권을 박탈한다.


🔔 shared_ptr의 문제점

  1. 순환 참조
    • 보통 그룹 객체와 소속 객체 사이에서 발생하는 순홤 참조.
    • 서로가 서로를 참조하는 순환 참조가 가능한데 이런 상황에선 메모리 해제가 제대로 되지 않는다.
  2. 멀티 쓰레드 안정성
    • 다수의 쓰레드에서 같은 객체를 참조하는 경우가 있을 수 있다. 이때 읽기는 안전하지만 쓰기는 안전하지 않다.

koriel님 블로그를 참고했습니다



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

맨 위로 이동하기

댓글남기기