M.7 std::shared_ptr

주홍영·2022년 3월 24일
0

Learncpp.com

목록 보기
197/199

https://www.learncpp.com/cpp-tutorial/stdshared_ptr/

unique_ptr이 하나의 std::unique_ptr 객체에서 resource를 manage 하게 설계된 것과 다르게
std::shared_ptr은 복수의 smart pointer가 resource를 co-owning할 수 있게 설계되었다

이 말은 복수의 std::shared_ptr object가 same resource를 pointing하는 것이 가능하다는 것이다. 내부적으로 std::shared_ptr은 몇개의 shared_ptr이 resource를 manage하고 있는지 계속해서 추적한다. 적어도 하나의 std::shared_ptr이 manage하고 있는 한 resource는 deallocated되지 않는다. 마지막 남은 shared_ptr마저 destory되거나 out of scope인 경우에 비로소 resource를 deallocate되도록 설계되어 있다

std::shared_ptr역시 < memory > header에 정의되어 있다

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
	Resource() { std::cout << "Resource acquired\n"; }
	~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
	// allocate a Resource object and have it owned by std::shared_ptr
	Resource *res = new Resource;
	std::shared_ptr<Resource> ptr1{ res };
	{
		std::shared_ptr<Resource> ptr2 { ptr1 }; // make another std::shared_ptr pointing to the same thing

		std::cout << "Killing one shared pointer\n";
	} // ptr2 goes out of scope here, but nothing happens

	std::cout << "Killing another shared pointer\n";

	return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed

위처럼 ptr1과 ptr2가 same object를 관리하고 있지만 문제가 발생하지 않고
마지막 ptr1이 파괴되었을 때 비로소 Resource 또한 deallocate된다
따라서 출력은 다음과 같다

Resource acquired
Killing one shared pointer
Killing another shared pointer
Resource destroyed

근데 위 프로그램에서 주목할 점은 res로 직접 ptr2를 만든 것이 아닌 ptr1으로 initializaiton을 통해서 객체를 생성했다는 것이다
이는 매우 중요한 것이다
다음의 예시를 보자

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
	Resource() { std::cout << "Resource acquired\n"; }
	~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
	Resource *res = new Resource;
	std::shared_ptr<Resource> ptr1 { res };
	{
		std::shared_ptr<Resource> ptr2 { res }; // create ptr2 directly from res (instead of ptr1)

		std::cout << "Killing one shared pointer\n";
	} // ptr2 goes out of scope here, and the allocated Resource is destroyed

	std::cout << "Killing another shared pointer\n";

	return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed again

위 프로그램은 다음의 출력을 준다

Resource acquired
Killing one shared pointer
Resource destroyed
Killing another shared pointer
Resource destroyed

그리고 crash가 발생한다
차이는 res를 이용해서 직접 ptr1과 ptr2를 만들었다는 것이다
결과적으로 same object를 pointing하고 있으나 둘 사이에는 서로의 존재를 알 수가 없다
따라서 ptr2가 scope를 벗어나면서 이미 Resource는 deallocate된 상황이고
이때 main함수가 종료되면서 다시 delete를 시도하면 crash가 발생하게 된다

다행히도 이는 쉽게 피할 수 있다
shared::ptr이 필요한 경우 copy를 통해서 생성하면된다

Best practice

Always make a copy of an existing std::shared_ptr if you need more than one std::shared_ptr pointing to the same resource.
명심하자!!!

std::make_shared

std::make_unique()와 비슷하게 std::make_shared도 가능하다

#include <iostream>
#include <memory> // for std::shared_ptr

class Resource
{
public:
	Resource() { std::cout << "Resource acquired\n"; }
	~Resource() { std::cout << "Resource destroyed\n"; }
};

int main()
{
	// allocate a Resource object and have it owned by std::shared_ptr
	auto ptr1 { std::make_shared<Resource>() };
	{
		auto ptr2 { ptr1 }; // create ptr2 using copy of ptr1

		std::cout << "Killing one shared pointer\n";
	} // ptr2 goes out of scope here, but nothing happens

	std::cout << "Killing another shared pointer\n";

	return 0;
} // ptr1 goes out of scope here, and the allocated Resource is destroyed

make_shared를 사용하는 이유는 make_unique를 사용하는 이유와 같다
더 간단하고 안전하기 때문이다
위 방법을 통해서 객체를 할당하면 동일한 객체를 manage하도록 만들 수가 없다는 장점이 있다

Digging into std::shared_ptr

std::unique_ptr이 내부에 하나의 pointer만 사용하는 것과 다르게
std::shared_ptr의 경우 두개의 pointer를 내부적으로 사용한다
한 포인터는 관리하고 있는 resource를 pointing하고
다른 하나는 control block을 pointing한다
control block은 dynamically allocated object 인데,
리소스를 가리키는 std::shared_ptr의 수를 포함하여 많은 항목을 추적하고 있다
shared::ptr이 생성될 때 따로따로 위의 두 memory가 할당이 되지만
make_shared를 쓰는 경우 single memory allocation으로 최적화 될 수 있고
이는 더 나은 성능을 끌어낸다

위 처럼 control block의 존재 때문에
동일한 resource로 초기화 시키는 shared_ptr들은 copy를 통해서 만들어지는 것이 아니면
각자의 control block을 가지게 된다. 따라서 crash가 발생하도록 하는 원인이 된다

그러나 copy assignment로 생성되면 control block이 올바르게 업데이트될 수 있기 때문에
우리가 의도한 shared_ptr의 기능을 사용할 수 있다

Shared pointers can be created from unique pointers

unique pointer의 경우 move를 통해서 shared_ptr을 생성할 수 있다

그러나 안전하게 그 반대의 경우를 구현하는 것은 불가능하다

The perils(위험성) of std::shared_ptr

unique ptr과 마찬가지로 dyanamically 할당해서 사용한다면
object가 scope를 벗어나며 파괴되지 않기 때문에 스마트 포인터의 기능의 의미가 없어진다
그런데 unique_ptr의 경우 한개의 스마트 포인터만을 걱정하면 됐지만
shared_ptr의 경우 복수의 ptr를 신경써야하므로 주의가 필요하다

Conclusion

std::shared_ptr은 same resource를 복수의 스마트 포인터에서 managing하고 싶을 때 사용한다
resource는 마지막 shared_ptr 마저 destory 되는 순간 deallocated된다

profile
청룡동거주민

0개의 댓글