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

AutoPtr<Resource> generateResource()  // AutoPtr<Resource> 타입을 리턴하는 함수
{
   // 10000000 의 length를 가진 Resource타입의 멤버를 가지는 AutoPtr 객체 생성
	AutoPtr<Resource> res(new Resource(10000000));

	return res;
}

int main()
{
	using namespace std;
	streambuf * orig_buf = cout.rdbuf();
	// cout.rdbuf(NULL); 화면에 출력되는 메세지들 끄기. 시간 어마어마하게 걸릴테니까 😎

	Timer timer;
	{
		AutoPtr<Resource> main_res; // ⭐
		main_res = generateResource(); // ⭐ generateResource() 리턴값은 R-value 
	}
		cout.rdbuf(orig_buf);
		cout << timer.elapsed() << endl; // 실행시간 재서 출력
	}
}

Resource.h

#include <iostream>
using namespace std;

class Resource
{
public:
	int * m_data = nullptr;
	unsigned m_length = 0;

public:
	Resource() // 기본 생성자
	{
		cout << "Resource constructed" << endl;
	}

	Resource(unsigned length) // 일반 매개변수 1개 생성자
	{
		cout << "Resource length constructed" << endl;
		this->m_data = new int[length];
		this->m_length = length;
	}

	Resource(const Resource &res) // 💎복사 생성자💎 
	{
		cout << "Resource copy constructed" << endl;
		
		Resource(res.m_length);

		for (unsigned i = 0; i < m_length; ++i)  // 내용물을 전부 깊은 복사 (시간이 꽤 걸림)
			m_data[i] = res.m_data[i];
	}
 
	~Resource()  // 소멸자
	{
		cout << "Resource destroyed" << endl;
	}

	Resource & operator = (Resource & res)  // 💎대입 연산자 오버로딩💎
	{
		cout << "Resource copy assignment" << endl;

		if (&res == this) return *this; // 대입하려는게 자기 자신이면 아무것도 안함
		
		if (this->m_data != nullptr) delete[] m_data; // 1. 내 자신의 m_data 비워주기

		m_length = res.m_length; // 2. 대입으로 넘겨받은 res의 length 로 내 length 갱신
		
		m_data = new int[m_length]; // 3. 비워진 내 자신의 m_data에 새로운 공간 할당받기
		for (unsigned i = 0; i < m_length; ++i) // 4. m_data내용물 넣기.
			m_data[i] = res.m_data[i]; //  대입으로 넘겨받은 res의 m_data 내용물들을 **내 m_data**에 깊은 복사

		return *this;
	}   
};

AutoPtr.h

#include <iostream>
using namespace std;

template<typename T>
class AutoPtr
{
public:
	T* m_ptr;

public:
	AutoPtr(T* ptr = nullptr)
		:m_ptr(ptr) 
	{
		cout << "AutoPtr default constructor" << endl;
	}
	~AutoPtr()
	{
		cout << "AutoPtr destructor" << endl;

		if (m_ptr != nullptr) delete m_ptr;
	}
	AutoPtr(const AutoPtr& a) // 💎복사 생성자💎
	{
		cout << "AutoPtr copy constructor" << endl;

		// deep copy
		m_ptr = new T;  // T가 Resource 타입으로 들어오면 m_ptr은 Resource 타입의 포인터
		*m_ptr = *a.m_ptr;  // ⭐Resource의 '대입 연산자 오버로딩 호출
	}
	AutoPtr& operator = (const AutoPtr& a) // 💎대입 연산자 오버로딩💎
	{
		cout << "AutoPtr copy assignment" << endl;
		
		if (&a == this)
			return *this;

		if (m_ptr != nullptr) delete m_ptr;

		// deep copy
		m_ptr = new T;  // 새로운 빈 공간 할당 받기. T가 Resource 타입으로 들어오면 m_ptr은 Resource 타입의 포인터
		*m_ptr = *a.m_ptr; // ⭐Resource의 '대입 연산자 오버로딩' 호출 

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

AutoPtr.h

template<typename T>
class AutoPtr
{
public:
	T* m_ptr;

public:
	AutoPtr(T* ptr = nullptr)
		:m_ptr(ptr) 
	{
		cout << "AutoPtr default constructor" << endl;
	}
	~AutoPtr()
	{
		cout << "AutoPtr destructor" << endl;

		if (m_ptr != nullptr) delete m_ptr;
	}

	AutoPtr(AutoPtr && a)  // ⭐이동생성자⭐ 
		:  m_ptr(a.m_ptr) // ⭐얕은 복사⭐ 그냥 대입만 하면 땡이다!
	{ 
		cout << "AutoPtr move constructor" << endl;

		a.m_ptr = nullptr; // really necessary? // 곧 사라질 애인데 nullptr로 굳이? => 깔끔하게 코딩하는 입장에서 안전하게 nullptr로.
	}

	AutoPtr& operator = (AutoPtr&& a)  // ⭐*이동 대입 연산자 오버로딩⭐ 
	{
		cout << "AutoPtr move assignment" << endl; // 집을 옮길 때 열쇠만 주는 격. pointer만 넘겨줌

		if (&a == this)
			return *this;

		// 공간은 비워줘야하는 것 똑같고 (delete 안하고 그냥 대입하면 메모리 누수가 발생할 수 있다)
		if (m_ptr != nullptr) delete m_ptr; 
		
		m_ptr = a.m_ptr; // ⭐얕은 복사⭐ 그냥 대입만 하면 땡이다! // resource의 pointer만 복사.
		a.m_ptr = nullptr; // 소유권 박탈

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