본문 바로가기

개발 공부/C++

따라하며 배우는 C++ 15. 의미론적 이동과 스마트 포인터

따라하며 배우는 C++ 15. 의미론적 이동과 스마트 포인터

 

15.1 이동의 의미와 스마트 포인터

 

#include <iostream>
#include "Resource.h"
using namespace std;

//RAII : resource acquisition is initialization
//RAII = new한 곳에서 delete를 해 주어야 한다는 원칙.
void doSomething()
{
	Resource* res = new Resource;
	//work with res
	delete res; //이렇게 할 경우 early return, exception을 하면
	//delete가 중복될 수도 있고, 까먹을 수도 있다.
	return;
}
int main()
{
	doSomething();
	return 0;
}

 

여기서 해결할 수 있는 건, 항상 관리해줄 수 있는 클래스를 새로 만드는 것이다.

원래 auto_ptr이 있지만, c++ 98에 들어와서 c++ 11에선 사용하지 않게 되었고, 17에선 완전히 안 쓰게 되었다.

동작하지 않는 경우가 많아서 다른 스마트 pointer가 도입되었다.

 

이 때 자동 생성 - delete가 안 되는 pointer를 dull pointer라고 한다.

위에서는 Constructor만 호출되고, Destructor는 호출되지 않는다.

 

Resource.h

#pragma once
#include <iostream>

class Resource
{
public:
	int m_data[100];
public:
	Resource()
	{
		std::cout << "Resource constructed" << std::endl;
	}
	~Resource()
	{
		std::cout << "Resource destroyed" << std::endl;
	}
};

 

AutoPtr.h

#pragma once
#include <iostream>
template<class T>
class AutoPtr
{
public:
	T* m_ptr = nullptr;
public:
	AutoPtr(T* ptr = nullptr)
		: m_ptr(ptr)
	{}

	~AutoPtr()
	{
		if (m_ptr != nullptr) delete m_ptr;
	}

	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
	bool isNull() const { return m_ptr == nullptr; }
};

Source.cpp

#include <iostream>
#include "Resource.h"
#include "AutoPtr.h"
using namespace std;

//RAII : resource acquisition is initialization
//RAII = new한 곳에서 delete를 해 주어야 한다는 원칙.
void doSomething()
{
	try
	{
		AutoPtr<Resource> res(new Resource);
		if (true);
			throw - 1;
	}
	catch (...)
	{

	}
	return;
}
int main()
{
	doSomething();
	return 0;
}

스마트 포인터를 사용하면 자동 destroy가 이루어진다.

 

#include <iostream>
#include "Resource.h"
#include "AutoPtr.h"
using namespace std;


int main()
{
	{
		AutoPtr<Resource> res1(new Resource);
		AutoPtr<Resource> res2;
		//메모리 초기화가 되지 않음.

		cout << std::boolalpha;
		cout << res1.m_ptr << endl;
		cout << res2.m_ptr << endl;
		res2 = res1;
		cout << res1.m_ptr << endl;
		cout << res2.m_ptr << endl;
	}
	return 0;
}

여기서 리소스는 한 번만 만들어진다.

그러나 뒤에서 nullptr이 리소스를 참조하면서, 두 개의 포인터가 같은 주소를 가리키게 된다.

문제는 res1이 영역 밖을 나가면서 먼저 메모리에서 해제되는데, 이것을 참조하는 res2가 참조할 곳이 없어지면서

런타임 에러를 발생시키는 것이다.

 

이것을 해결하는 방법에는 소유권을 이동시키는 것이 있다.

즉, res1이 잡고 있던 힙에 있는 메모리 자체를 res2로 이동시키는 것.

 

//Copy Constructor
	AutoPtr(AutoPtr& a)
	{
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr;
	}

	AutoPtr& operator = (AutoPtr& a)
	{
		if (&a == this) return *this;

		delete m_ptr;
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr;
		return *this;
	}

위는 AutoPtr.h의 일부이다.

이렇게 소유권을 이전시키는 것을 move semantics라고 한다.

 

syntax : 문법에 대한 것. 컴파일이 잘 되느냐 마느냐,

semantics : 기호적으론 같을 때, 언어적으론 의미가 다를 수 있다.

 

value semantics(copy semantics)

reference semantics(pointer)

move sematics(move)

즉 위에서 오버로딩한 = 는 copy constructor가 아니라 move를 구현한 것이다.

 

주의할 점은, 함수의 매개변수로 넣는 경우 어쩌면 객체가 사라질 수도 있다.

 

 

15.2 오른쪽 값-참조 R-value References

 

R-value reference는 곧 사라질 r-value들만 담는다(5 같은 단순 상수나, 함수의 return값인데

받아주는 변수가 없다면 곧 사라질 값들)

즉 이제 더 이상 쓰이지 않는 수이기 때문에, move를 해도 충돌이 발생하지 않는다.

 

다만 l-value는 전체적으로 쓰일 수 있는 변수일 경우가 많다.

l-value를 함부로 옮겨 버리면, 내용물을 딴 데로 옮겨버리면 해당 변수를 재사용할 경우 충돌이 일어날 수 있다.

 

 

#include <iostream>
using namespace std;

int getResult()
{
	return 100 * 100;
}

//l-value, r-value reference도 오버로딩 가능!
void doSomething(int& lref)
{
	cout << "L-value ref" << endl;
}

void doSomething(int&& rref)
{
	cout << "R-value ref" << endl;
}

int main()
{
	int x = 5; // l-value = r-value
				//여기서 r-value는 메모리를 갖고 있고
				//l-value는 문장이 끝나면 사라질 운명이다.
	int y = getResult();
	const int cx = 6;
	const int cy = getResult();

	// L-value references
	int& lr1 = x;	// Modifiable l-values
	//int &lr2 = cx;	// Non-modifiable l-values;
	//l-value가 const가 아닐 경우 const를 넣을 수 없다.
	//int &lr3 = 5;    // R-values
	//l-value에 대해서만 reference를 가질 수 있다.

	const int& lr4 = x; //Modifiable l-values
	const int& lr5 = cx; //Non-modifiable l-values
	const int& lr6 = 5; //R-values

	//R-value references
	//int &&rr1 = x;	//Modifiable l-values
	//int &&rr2 = cx;	//Non-modifiable l-values
	int&& rr3 = 5;		//R-values

	cout << rr3 << endl;
	rr3 = 10;
	cout << rr3 << endl; //출력도, 변경도 가능.

	int&& rrr = getResult();
	//곧 사라질 운명의 r-value를 담아주겠다.

	//const int &&rr4 = x;	//Modifiable l-values
	//const int &&rr5 = cx; //Non-modifiable l-values;
	const int&& rr6 = 5; //R-values
	//L.R value reference parameters
	doSomething(x);
	//doSomething(cx);
	doSomething(5);
	doSomething(getResult());
	return 0;
}

 

15.3 이동 생성자와 이동 대입 Move constructor and Move assignment

 

 

"AutoPtr.h"

#pragma once
#include <iostream>
template<class T>
class AutoPtr
{
public:
	T* m_ptr = nullptr;
public:
	AutoPtr(T* ptr = nullptr)
		: m_ptr(ptr)
	{
		std::cout << "AutoPtr default constructor " << std::endl;
	}

	~AutoPtr()
	{
		std::cout << "AutoPtr destructor " << std::endl;
		if (m_ptr != nullptr) delete m_ptr;
	}

	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
	bool isNull() const { return m_ptr == nullptr; }

	AutoPtr(const AutoPtr& a) //l-value Reference
	{
		//deep copy
		std::cout << "AutoPtr copy constructor " << std::endl;
		m_ptr = new T;
		*m_ptr = *a.m_ptr;
	}

	AutoPtr& operator = (const AutoPtr& a)
	{
		std::cout << "AutoPtr copy assignment  " << std::endl;
		if (&a == this) return *this; //prevent self - assignment

		if (m_ptr != nullptr) delete m_ptr;
		//deep copy
		m_ptr = new T;
		*m_ptr = *a.m_ptr;
		return *this;
	}

};

"Rersource.h"

#pragma once
#include <iostream>

class Resource
{
public:
	int* m_data = nullptr;
	unsigned m_length = 0;
public:
	Resource()
	{
		std::cout << "Resource default constructed" << std::endl;
	}
	Resource(unsigned length)
	{
		std::cout << "Resource length constructed" << std::endl;
		this->m_data = new int[length];
		this->m_length = length;
	}
	Resource(const Resource& res)
	{
		std::cout << "Resrouce copy constructor" << std::endl;
		Resource(res.m_length);
		for (unsigned i = 0; i < m_length; ++i)
			m_data[i] = res.m_data[i]; //깊은 복사
	}
	~Resource()
	{
		std::cout << "Resource destroyed" << std::endl;
		if (m_data != nullptr) delete[] m_data;
	}
	Resource& operator = (Resource& res)
	{
		std::cout << "Resource copy assignment" << std::endl;
		if (&res == this) return *this;
		if (this->m_data != nullptr) delete[] m_data;
		m_length = res.m_length;
		m_data = new int[m_length];
		for (unsigned i = 0; i < m_length; ++i)
			m_data[i] = res.m_data[i];
		return *this;
	}
};

"Timer.h"

#pragma once
#include <vector>
#include <string>
#include <chrono>
#include <iostream>
#include <random>
#include <algorithm>
using namespace std;
class Timer
{
	using clock_t = chrono::high_resolution_clock;
	using second_t = chrono::duration<double, ratio<1>>;
	chrono::time_point<clock_t> start_time = clock_t::now();
public:
	void elapsed()
	{
		chrono::time_point<clock_t> end_time = clock_t::now();
		cout << chrono::duration_cast<second_t>(end_time - start_time).count() << endl;
	}
};

"Source.cpp"

#include "Timer.h"
#include "AutoPtr.h"
#include "Resource.h"
using namespace std;

AutoPtr<Resource> generateResource()
{
	AutoPtr<Resource> res(new Resource(5));
	return res;
}

int main()
{
	using namespace std;
 	streambuf* orig_buf = cout.rdbuf();
	//cout.rdbuf(NULL); //diconnect cout from buffer
	Timer timer;

	{
		AutoPtr<Resource> main_res;
		main_res = generateResource();
	}

	cout.rdbuf(orig_buf);
	timer.elapsed();
	return 0;
}

 

 

#pragma once
#include <iostream>
template<class T>
class AutoPtr
{
public:
	T* m_ptr = nullptr;
public:
	AutoPtr(T* ptr = nullptr)
		: m_ptr(ptr)
	{
		std::cout << "AutoPtr default constructor " << std::endl;
	}

	~AutoPtr()
	{
		std::cout << "AutoPtr destructor " << std::endl;
		if (m_ptr != nullptr) delete m_ptr;
	}

	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
	bool isNull() const { return m_ptr == nullptr; }

	//move assignment
	AutoPtr& operator=(AutoPtr&& a)
	{
		std::cout << "AutoPtr move assignment " << std::endl;
		if (&a == this) return *this;
		if (!m_ptr) delete m_ptr;

		//shallow copy
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr;
		return *this;
	}
	
	AutoPtr(AutoPtr&& a)
		: m_ptr(a.m_ptr)
	{
		a.m_ptr = nullptr;
		std::cout << "AutoPtr move constructor" << std::endl;
	}
	//AutoPtr(const AutoPtr& a) //l-value Reference
	//{
	//	//deep copy
	//	std::cout << "AutoPtr copy constructor " << std::endl;
	//	m_ptr = new T;
	//	*m_ptr = *a.m_ptr;
	//}

	//AutoPtr& operator = (const AutoPtr& a)
	//{
	//	std::cout << "AutoPtr copy assignment  " << std::endl;
	//	if (&a == this) return *this; //prevent self - assignment

	//	if (m_ptr != nullptr) delete m_ptr;
	//	//deep copy
	//	m_ptr = new T;
	//	*m_ptr = *a.m_ptr;
	//	return *this;
	//}

};

여기서 "AutoPtr.h"를 위와 같이 변경한다.

assignment와 copy construct의 parameter를 R-value로 받는 것이다.

deep copy를 하는 대신, shallow copy만 하는 것!

semantic 상으론 move만 되는 것이다.

 

 

 

 

15.4 std::move

 

 

"Resource.h"

#pragma once
#include <iostream>

class Resource
{
public:
	int* m_data = nullptr;
	unsigned m_length = 0;
public:
	Resource()
	{
		std::cout << "Resource default constructed" << std::endl;
	}
	Resource(unsigned length)
	{
		std::cout << "Resource length constructed" << std::endl;
		this->m_data = new int[length];
		this->m_length = length;
	}
	Resource(const Resource& res)
	{
		std::cout << "Resrouce copy constructor" << std::endl;
		Resource(res.m_length);
		for (unsigned i = 0; i < m_length; ++i)
			m_data[i] = res.m_data[i]; //깊은 복사
	}
	~Resource()
	{
		std::cout << "Resource destroyed" << std::endl;
		if (m_data != nullptr) delete[] m_data;
	}
	Resource& operator = (Resource& res)
	{
		std::cout << "Resource copy assignment" << std::endl;
		if (&res == this) return *this;
		if (this->m_data != nullptr) delete[] m_data;
		m_length = res.m_length;
		m_data = new int[m_length];
		for (unsigned i = 0; i < m_length; ++i)
			m_data[i] = res.m_data[i];
		return *this;
	}
	void print()
	{
		for (unsigned i = 0; i < m_length; ++i)
			std::cout << m_data[i] << " ";
		std::cout << std::endl;
	}
	void setAll(const int& v)
	{
		for (unsigned i = 0; i < m_length; ++i)
			m_data[i] = v;
	}
};

 

"AutoPtr.h"

#pragma once
#include <iostream>
template<class T>
class AutoPtr
{
public:
	T* m_ptr = nullptr;
public:
	AutoPtr(T* ptr = nullptr)
		: m_ptr(ptr)
	{
		std::cout << "AutoPtr default constructor " << std::endl;
	}

	~AutoPtr()
	{
		std::cout << "AutoPtr destructor " << std::endl;
		if (m_ptr != nullptr) delete m_ptr;
	}

	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
	bool isNull() const { return m_ptr == nullptr; }

	//move assignment
	AutoPtr& operator=(AutoPtr&& a)
	{
		std::cout << "AutoPtr move assignment " << std::endl;
		if (&a == this) return *this;
		if (!m_ptr) delete m_ptr;

		//shallow copy
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr;
		return *this;
	}
	
	AutoPtr(AutoPtr&& a)
		: m_ptr(a.m_ptr)
	{
		a.m_ptr = nullptr;
		std::cout << "AutoPtr move constructor" << std::endl;
	}
	AutoPtr(const AutoPtr& a) //l-value Reference
	{
		//deep copy
		std::cout << "AutoPtr copy constructor " << std::endl;
		m_ptr = new T;
		*m_ptr = *a.m_ptr;
	}

	AutoPtr& operator = (const AutoPtr& a)
	{
		std::cout << "AutoPtr copy assignment  " << std::endl;
		if (&a == this) return *this; //prevent self - assignment

		if (m_ptr != nullptr) delete m_ptr;
		//deep copy
		m_ptr = new T;
		*m_ptr = *a.m_ptr;
		return *this;
	}

};

 

"Source.cpp"

 

#include "Timer.h"
#include "AutoPtr.h"
#include "Resource.h"
using namespace std;

AutoPtr<Resource> generateResource()
{
	AutoPtr<Resource> res(new Resource(5));
	return res;
}

int main()
{
	{
		AutoPtr<Resource> res1(new Resource(10000000));
		cout << res1.m_ptr << endl;
		AutoPtr<Resource> res2 = res1;
		cout << res1.m_ptr << endl;
		cout << res2.m_ptr << endl;
	}
	return 0;
}

위에선 copy constructor와 copy assignment가 실행된다.

여기서 move constructor를 실행시키기 위해선

int main()
{
	{
		AutoPtr<Resource> res1(new Resource(10000000));
		cout << res1.m_ptr << endl;
		AutoPtr<Resource> res2 = std::move(res1);
		cout << res1.m_ptr << endl;
		cout << res2.m_ptr << endl;
	}
	return 0;
}

 

로 메인을 고친다.

결과는 다음과 같다.

 

 

#include "Timer.h"
#include "AutoPtr.h"
#include "Resource.h"
using namespace std;

template<class T>
void MySwap(T& a, T& b)
{
	T tmp = a;
	a = b;
	b = tmp;

}

AutoPtr<Resource> generateResource()
{
	AutoPtr<Resource> res(new Resource(5));
	return res;
}

int main()
{
	{
		AutoPtr<Resource> res1(new Resource(3));
		res1->setAll(3);
		AutoPtr<Resource> res2(new Resource(5));
		res2->setAll(5);
		res1->print();
		res2->print();
		MySwap(res1, res2);
		res1->print();
		res2->print();
	}
	return 0;
}

위 예제는 swap 관련 예제이다.

 

copy만 사용할 경우 과정이 아래와 같이 복잡해진다.

 

move를 이용해서 swop를 사용할 수도 있다!

#include "Timer.h"
#include "AutoPtr.h"
#include "Resource.h"
using namespace std;

template<class T>
void MySwap(T& a, T& b)
{
	T tmp{ std::move(a) };
	a = std::move(b);
	b = std::move(tmp);
}

AutoPtr<Resource> generateResource()
{
	AutoPtr<Resource> res(new Resource(5));
	return res;
}

int main()
{
	{
		AutoPtr<Resource> res1(new Resource(3));
		res1->setAll(3);
		AutoPtr<Resource> res2(new Resource(5));
		res2->setAll(5);
		res1->print();
		res2->print();
		MySwap(res1, res2);
		res1->print();
		res2->print();
	}
	return 0;
}

 

move를 한 이후엔 이렇게 간단하게 swop을 실행할 수 있다.

 

#include "Timer.h"
#include "AutoPtr.h"
#include "Resource.h"
using namespace std;

template<class T>
void MySwap(T& a, T& b)
{
	T tmp{ std::move(a) };
	a = std::move(b);
	b = std::move(tmp);
}

AutoPtr<Resource> generateResource()
{
	AutoPtr<Resource> res(new Resource(5));
	return res;
}

int main()
{
	{
		vector<string> v;
		string str = "Hello";

		v.push_back(str); //l-value
		cout << str << endl;
		cout << v[0] << endl;
		v.push_back(std::move(str)); //r-value
		cout << str << endl; //사라짐
		cout << v[0] << " " << v[1] << endl;
	}
	return 0;
}

 

vector 내부에는 move가 구현이 되어 있다.

 

 

 

15.5 std::unique_ptr

 

#include <iostream>
#include <memory>
#include "Resource.h"

auto doSomething()
{
	//auto res1 = std::make_unique<Resource>(5);
	//return res;
	return std::unique_ptr<Resource>(new Resource(5));
	//return std::make_unique<Resource>(5);
}

void doSomething2(std::unique_ptr<Resource>& res)
//void doSomething2(Resource * res)
{
	res->setAll(10);
}
int main()
{
	std::unique_ptr<Resource> res(new Resource(10000000));
	return 0;
}

 

 

unique ptr은 자동으로 delete 처리를 해 준다.

 

#include <iostream>
#include <memory>
#include "Resource.h"

auto doSomething()
{
	//auto res1 = std::make_unique<Resource>(5);
	//return res;
	//위와 같이 리턴해도 된다.

	return std::unique_ptr<Resource>(new Resource(5));
	//return std::make_unique<Resource>(5);
	//위와 같이 return하는 것이 가장 간단한 방법.
}

void doSomething2(std::unique_ptr<Resource>& res)
//void doSomething2(Resource * res)
{
	res->setAll(10);
}
int main()
{
	//std::unique_ptr<Resource> res(new Resource(10000000));

	{
		std::unique_ptr<int> upi(new int);
		//std::unique_ptr<Resource> res1(new Resource(5));
		//위와 같이 선언해도 작동한다.

		//auto *ptr = new Resource(5);
		//std::unique_ptr<Resource> res1(ptr);
		//위와 같이 선언해도 작동한다.

		//단, unique_ptr<Resource res1 = new Resource(5); 는 불가능

		auto res1 = std::make_unique<Resource>(5);
		//권장하는 방법은 위처럼 선언하는 것이다.

		//auto res1 = doSomething();
		//함수로부터 받을 수 있다.

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

		std::unique_ptr<Resource> res2;
		std::cout << std::boolalpha;
		std::cout << static_cast<bool>(res1) << std::endl; //null포인터 아님
		std::cout << static_cast<bool>(res2) << std::endl; //null포인터임

		// res2 = res1;
		// 유니크 포인터는 소유권이 한 군데에만 있을 수 있어서
		//복사가 안 된다. copy semantics는 사용 불가.
		res2 = std::move(res1);
		
		std::cout << std::boolalpha;
		std::cout << static_cast<bool>(res1) << std::endl; //null포인터 아님
		std::cout << static_cast<bool>(res2) << std::endl; //null포인터임
		
		if (res1 != nullptr) res1->print();
		if (res2 != nullptr) res2->print();
		//(*res2).print();처럼 디레퍼런싱도 내부적으로 구현되어 있어서
		//사용 가능하다.
	}
	return 0;
}

 

#include <iostream>
#include <memory>
#include "Resource.h"

auto doSomething()
{
	return std::unique_ptr<Resource>(new Resource(5));
}

//unique ptr의 reference를 받고 있다.
void doSomething2(std::unique_ptr<Resource>& res)
//void doSomething2(Resource * res)
{
	res->setAll(10);
}
int main()
{
	{
		auto res1 = std::make_unique<Resource>(5);
		res1->setAll(1);
		res1->print();

		doSomething2(res1);
		res1->print();
	}
	return 0;
}

 

 

void doSomething2(std::unique_ptr<Resource> res)

이 때 위와 같이 함수를 바꾸면(그냥 값으로) doSomething2(res1)을 할 수 없다.

unique ptr은 복사가 불가능한 형이기 때문에.

 

해결방법)

doSomething2(std::move(res1));

이 경우엔 res1의 값이 빼앗기기 때문에 null 값이 되어 버린다.

(함수가 호출되었다 제어권이 메인이 되면 Resource가 아예 해제되어 버린다.)

 

이럴 경우엔 다시 돌려받으면 된다.

res1 = doSomething2(std::move(res1));처럼.

 

#include <iostream>
#include <memory>
#include "Resource.h"

auto doSomething()
{
	return std::unique_ptr<Resource>(new Resource(5));
}

//unique ptr의 reference를 받고 있다.
//void doSomething2(std::unique_ptr<Resource>& res)
void doSomething2(Resource * res)
{
	res->setAll(10);
}
int main()
{
	{
		auto res1 = std::make_unique<Resource>(5);
		res1->setAll(1);
		res1->print();

		//get은 내부적으로 resource의 포인터를 얻을 수도 있다.
		//l-value reference 보낸 것처럼 작동함.
		doSomething2(res1.get());
		res1->print();
	}
	return 0;
}

int main()
{
	{
		Resource* res = new Resource;
		std::unique_ptr<Resource> res1(res);
		std::unique_ptr<Resource> res2(res);

		delete res;
	}
	return 0;
}

이런 경우엔 안 된다.

소유권을 하나에게만 줘야 하며, 알아서 delete하기 때문에 두 번 지우려고 시도해서도 안 된다.

 

 

15.6 std::shared_ptr

 

#include <iostream>
#include "Resource.h"

int main()
{
	Resource* res = new Resource(3);
	res->setAll(1);
    {
		std::shared_ptr<Resource> ptr1(res);
		//shared pointner는 포인터가 몇 군데서 공유되는지
		//내부적으로 카운트할 수 있다.
		//auto ptr1 = std::make_shard<Resource>(3);
		//ptr1->setAll(1);

		ptr1->print();

		{
			std::shared_ptr<Resource> ptr2(ptr1);
			//std::shared_ptr<Resource> ptr2(res);
			//auto ptr2 = ptr1;

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

			std::cout << "Going out of the block" << std::endl;
		}
		ptr1->print();
		std::cout << "Going out of the outer block" << std::endl;
	}
	return 0;
}

 

res가 main에서 작성되고

shared_ptr ptr1이 outer block 안에, ptr2가 inner block 안에 선언되었다.

여기서 ptr1이 outer block 안에서 해제되면 main에서 작성된 res까지 해제되어 버린다.

 

또한 여기서

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

ptr2가 ptr1을 거쳐 만들어지는 것이 아닌 res로부터 만들어진다면

ptr1은 ptr2도 res에 대한 소유권을 갖고 있다는 사실을 알 방법이 없다.

이렇게 하면 문제가 생긴다 innter block에서 ptr2이 해제되며 res까지 해제되어 버릴 수 있기 때문이다.

(남은 ptr1이 참조할 방도가 없음)

 

 

여기서도 선언에서,

std::shared_ptr<Resource> ptr1(res);

이것보다

auto ptr1 = std::make_shared<Resource>(3);

의 방법이 더 선호된다.

 

parameter에서도,

soSomething(std::unique_ptr<Resource>(new Resource(1000000)));
soSomething(std::make_unique<Resource>(1000000));

전자보다는 후자를 선호한다.

 

#include <iostream>
#include "Resource.h"

int main()
{
	
	{
		auto ptr1 = std::make_shared<Resource>(3);
		ptr1->setAll(1);
		ptr1->print();
		{
			auto ptr2 = ptr1;
			ptr2->setAll(3);
			ptr2->print();

			std::cout << "Going out of the block" << std::endl;
		}
		ptr1->print();
		std::cout << "Going out of the outer block" << std::endl;
	}
	return 0;
}

이렇게 하면 깔끔하다.

 

 

 

 

15.7 순환 의존성 문제와 std::weak_ptr

 

#include <iostream>
#include <memory>
#include <string>

class Person
{
	std::string m_name;
	std::shared_ptr<Person> m_partner;
	//std::weak_ptr<Person> m_partnet;
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); //메모리 leak 발생
	return 0;
}

 

Person이 내부적으로 shared_ptr로 다른 사람을 가지고 있음.

이 때 Lucy와 Ricky가 파트너이고, Lucy 메모리를 먼저 해제하려는 상태에서,

아직 Ricky과 파트너로서 Lucy를 참조하고 있으므로 Lucy를 지울 수 없는 꼬인 상태가 되는 것.

 

#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;
	}

	const std::shared_ptr<Person> getPartner() const
	{
		return m_partner.lock();
	}
};

int main()
{
	auto lucy = std::make_shared<Person>("Lucy");
	auto ricky = std::make_shared<Person>("Ricky");

	partnerUp(lucy, ricky); 
	return 0;
}

 

이것을 weak pointer로 바꾸면 해결된다.

하지만 직접 참조할 수 없고, shared pointer로 바꾸어 return 해주어야 뭔가를 할 수 있다.

ex. std::cout<<lucy->getPartner()->getName()