C++ R-Value를 이용한 이동 대입


C++ R-Value를 이용한 이동 대입

L-Value를 이용해서 밑에와 같이 간단한 프로그램을 만들었다고 치자.

main.cpp

#include "Resource.h"
#include "AutoPtr.h"
#include "Timer.h"
AutoPtr<Resource> generateResource()
{
	AutoPtr<Resource> res(new Resource(10000000));
	return res;
}

int main()
{
	Timer timer;
	{
		AutoPtr main_res;
		main_res = generateResource();
	}
	timer.elapsed();
}


AutoPtr.h

#pragma once
#include <iostream>

template<class T>
class AutoPtr
{
	T *m_ptr;
public:
	AutoPtr(T *ptr = nullptr) : m_ptr(ptr)
	{
		std::cout << "AutoPtr 기본 생성자" << std::endl;
	}
	~AutoPtr()
	{
		std::cout << "AutoPtr 소멸자" << std::endl;
		if (m_ptr != nullptr) delete m_ptr;
	}
	AutoPtr(AutoPtr &a)
	{
		std::cout << "AutoPtr 복사 생성자" << std::endl;
		m_ptr = new T;
		*m_ptr = *a.m_ptr;
	}
	AutoPtr& operator=(const AutoPtr &a)
	{
		std::cout << "AutoPtr = 연산자 오버로딩" << std::endl;
		if (&a == this)
			return *this;

		if(m_ptr != nullptr) delete m_ptr;

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

Resource.h

#pragma once
#include <iostream>

class Resource
{
public:
	int *m_data = nullptr;
	unsigned m_length = 0;
public:
	Resource()
	{
		std::cout << "Resource 기본 생성자" << std::endl;
	}
	Resource(unsigned length)
	{
		std::cout << "Resource 길이 생성자" << std::endl;
		m_data = new int[length];
		m_length = length;
	}
	Resource(const Resource &res)
	{
		std::cout << "Resource 복사 생성자" << 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 소멸자" << std::endl;
		if (m_data != nullptr) delete[] m_data;
	}
	Resource& operator=(Resource &res)
	{
		std::cout << "Resource = 연산자 오버로딩" << std::endl;
		if (&res == this) return *this;
		if (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;
	}
};

이 프로그램은 생성자가 언제 호출되고 소멸자가 언제 호출되고 연산자 오버로딩이 언제 호출되고 이런것을 알수있게 예제를 하나 작성하였다.



이 예제를 통해서 생성자가 언제 호출되고 사라지는지 미리 예측을 해보고 실행을 해보자.


디버깅 모드와 릴리즈 모드를 각각 실행해서 출력하는 내용과 시간을 보자.


디버깅 모드

릴리즈 모드


역시 릴리즈 모드는 엄청 빠르다.


그럼 이것을 조금 더 빠르게!! 더 빠르게 할 수가 있다.

어떻게 하냐면! R-Value를 이용해서 더 빠르게 해보자.

R-Value는 임시로 생성되는 메모리를 이용하는 것이다.


위의 코드를 보면 임시로 생성되는 메모리를 볼 수 있을것이다.

R-Value는 임시로 생성되는 메모리를 이용해서 더 빠르게 할 수 있다.


R-Value를 이용을 하기 위해서 AutoPtr.h 파일을 조금 수정을 했다.


#pragma once
#include <iostream>

template<class T>
class AutoPtr
{
	T *m_ptr;
public:
	AutoPtr(T *ptr = nullptr) : m_ptr(ptr)
	{
		std::cout << "AutoPtr 기본 생성자" << std::endl;
	}
	~AutoPtr()
	{
		std::cout << "AutoPtr 소멸자" << std::endl;
		if (m_ptr != nullptr) delete m_ptr;
	}
	AutoPtr(AutoPtr &&a) : m_ptr(a.m_ptr)
	{
		a.m_ptr = nullptr;
		std::cout << "AutoPtr 이동 생성자" << std::endl;
	}
	AutoPtr& operator=(AutoPtr &&a)
	{
		std::cout << "AutoPtr 이동 연산자 오버로딩" << std::endl;
		if (&a == this)
			return *this;

		if (!m_ptr) delete m_ptr;
		m_ptr = a.m_ptr;
		a.m_ptr = nullptr;
		return *this;
	}
	T& operator*() const { return *m_ptr; }
	T* operator->() const { return m_ptr; }
	bool isNull() const { return m_ptr == nullptr; }
};

코드 상으로 보면 이전 AutoPtr과 다를게 거의 없다.

한번 실행을 해보자.


R-Value 디버깅 모드

R-Value 릴리즈 모드


보이는가? Resource 생성자는 한번만 호출이 되는것을?

그리고 속도도 엄청 빠르다.


R-Value를 이용하면 속도가 엄청 빠르게 되고 메모리도 절약?이 되는 효과를 볼 수 있다.



댓글

Designed by JB FACTORY