M.3 Move constructors and move assignment

주홍영·2022년 3월 22일
0

Learncpp.com

목록 보기
193/199

https://www.learncpp.com/cpp-tutorial/move-constructors-and-move-assignment/

lesson M.1에서 우리는 std::auto_ptr에 대해서 살펴보았다
여기서 move semantics에 대한 필요성에 대해서도 알아보았고
copy semantics로 이를 구현할 때 발생하는 단점에 대해서도 살펴보았다

이번 레슨에서는 c++11에서 move constructor와 move assignment를 이용해 어떻게 이러한 문제를 해결하는지 살펴본다

Move constructors and move assignment

c++11에서는 move semantics를 위해 두가지 새로운 function이 정의되어 있다
move constructor와 move assignment opertor이다
copy constructor와 copy assignment의 목적이 object의 copy를 만들어 다른 object를 만드는 것에 있었다면, move constructor와 move assignment는 ownership을 옮기는데에 그 목적이 있다

move constructor와 assignment를 정의하는 것은 copy류와 비슷하다
그러나 copy가 l-value ref로 parameter를 설정하는데에 반해
move는 r-value ref로 parameter를 설정한다

여기 예시가 있다
비교를 위해 deep copying copy constructor, assignment와
move constructor, assignment가 있다

#include <iostream>

template<class T>
class Auto_ptr4
{
	T* m_ptr;
public:
	Auto_ptr4(T* ptr = nullptr)
		:m_ptr(ptr)
	{
	}

	~Auto_ptr4()
	{
		delete m_ptr;
	}

	// Copy constructor
	// Do deep copy of a.m_ptr to m_ptr
	Auto_ptr4(const Auto_ptr4& a)
	{
		m_ptr = new T;
		*m_ptr = *a.m_ptr;
	}

	// Move constructor
	// Transfer ownership of a.m_ptr to m_ptr
	Auto_ptr4(Auto_ptr4&& a) noexcept
		: m_ptr(a.m_ptr)
	{
		a.m_ptr = nullptr; // we'll talk more about this line below
	}

	// Copy assignment
	// Do deep copy of a.m_ptr to m_ptr
	Auto_ptr4& operator=(const Auto_ptr4& a)
	{
		// Self-assignment detection
		if (&a == this)
			return *this;

		// Release any resource we're holding
		delete m_ptr;

		// Copy the resource
		m_ptr = new T;
		*m_ptr = *a.m_ptr;

		return *this;
	}

	// Move assignment
	// Transfer ownership of a.m_ptr to m_ptr
	Auto_ptr4& operator=(Auto_ptr4&& a) noexcept
	{
		// Self-assignment detection
		if (&a == this)
			return *this;

		// Release any resource we're holding
		delete m_ptr;

		// Transfer ownership of a.m_ptr to m_ptr
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr; // we'll talk more about this line below

		return *this;
	}

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

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

Auto_ptr4<Resource> generateResource()
{
	Auto_ptr4<Resource> res(new Resource);
	return res; // this return value will invoke the move constructor
}

int main()
{
	Auto_ptr4<Resource> mainres;
	mainres = generateResource(); // this assignment will invoke the move assignment

	return 0;
}

위의 프로그램의 경우 generateResource()에서 r-value가 생성됨을 알 수 있다
따라서 move assignment operator가 호출된다
이때 우리는 copy constructor와 다르게 간단하게 move semantics를 실현한다

만약 copy가 호출된 경우 출력은 다음과 같고

Resource acquired
Resource acquired
Resource destroyed
Resource acquired
Resource destroyed
Resource destroyed

move가 호출된 경우 출력은 다음과 같다

Resource acquired
Resource destroyed

When are the move constructor and move assignment called?

move constructor, assignment는 이에 대한 function이 정의되어 있고 construction 혹은 assignment의 argument로 r-value를 전달 받을 때 호출된다
대부분의 경우 r-value는 literal이거나 temporary value, anonymous object이다

만약 정의된 move constructor나 move assignment가 없는 경우 default로 호출되지 안흔다

The key insight behind move semantics

argument가 l-value인 경우 object를 construct하거나 assignment하는 과정에서
가장 합리적인 방법은 l-value를 copy해서 object를 생성하는 것이다
왜나하면 우리는 l-value를 대체하는 것이 안전하다고 확답을 내는 것이 불가능하기 때문이다
추후에 프로그램에서 다시 사용될 수도 있기 때문에...

그러나 r-value인 경우 r-value는 temporary 값이므로 move를 통한 방법이 이치에 맞다
어차피 expression을 벗어나면 destroy되기 때문이다

c++11에서는 r-value와 l-value를 나눠줌으로써 우리에게 더 smart하고 효율적인 결정을 내릴 수 있도록 가능성을 열어두었다

남은 내용이 많지만 여기서 생략한다...

profile
청룡동거주민

0개의 댓글