15.3 이동 생성자(Move constructors)와 이동 대입(Move assignment)
#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; }
};