본문 바로가기

Information Technology/C++

[C++] std::forward

이 글은 개인의 학습을 목적으로 정리한 글입니다. 이점 참고하고 읽어주세요;)


#include <iostream>
#include <vector>
#include <utility>

using namespace std;

struct MyStruct
{};

void func(MyStruct& s)
{
	cout << "Pass by L-ref" << endl;
}

void func(MyStruct&& s)
{
	cout << "Pass by R-ref" << endl;
}

int main()
{
	MyStruct s;

	func(s);	// L-value
	func(MyStruct());	// R-value. 초기화 단계에서 생성되고 바로 다른 곳으로 넘어갈 운명?이기 때문에 R-value

	return 0;
}


template<typename T>	// 컴파일러가 템플리타이즈 함수는 L, R-value 분류를 못함
void func_wrapper(T t)
{
	func(t);
}

int main()
{
	MyStruct s;

	func_wrapper(s);
	func_wrapper(MyStruct());

	return 0;
}

템플릿을 사용하는 함수에 대해서는 컴파일러가 L-value와 R-value를 구분하지 못함


template<typename T>
void func_wrapper(T&& t)
{
	func(std::forward<T>(t));	
    // 전달 인자가 L-value로 들어오면 L-value로, R-value로 들어오면 R-value로 처리해줌
}


#include <iostream>
#include <vector>
#include <utility>
#include <utility>

using namespace std;

class CustomVector
{
public:
	unsigned n_data = 0;	// 벡터의 크기
	int* ptr = nullptr;	// 벡터가 저장될 주소

	CustomVector(const unsigned& _n_data, const int& _init = 0)
	{
		cout << "Constructor" << endl;

		init(_n_data, _init);	// 생성자에서 직접 초기화를 하기보다는, 생성자 외부에 초기화 함수를 따로 두는 것이 더욱 편리함
	}

	CustomVector(CustomVector& l_input)	// copy-constructor
	{
		cout << "Copy constructor" << endl;

		init(l_input.n_data);

		for (unsigned i = 0; i < n_data; ++i)
		{
			ptr[i] = l_input.ptr[i];
		}
	}

	CustomVector(CustomVector&& r_input)
	{
		cout << "Move constructor" << endl;

		n_data = r_input.n_data;
		ptr = r_input.ptr;

		r_input.n_data = 0;
		r_input.ptr = nullptr;	// 더 이상 필요가 없기 때문에 삭제
	}

	~CustomVector()
	{
		delete[] ptr;
	}

	void init(const unsigned& _n_data, const int& _init = 0)
	{
		n_data = _n_data;
		ptr = new int[n_data];
		for (unsigned i = 0; i < n_data; ++i)	// for문으로 초기값을 받아와서 저장
			ptr[i] = _init;
	}
};

int main()
{
	CustomVector my_vec(10, 1024);

	CustomVector temp(my_vec);	// L-value로 들어옴

	cout << my_vec.n_data << endl;	// 그대로 존재
}

L-value로 temp에 my_vec을 전달했기 때문에 my_vec의 n_data가 그대로 출력됨


int main()
{
	CustomVector my_vec(10, 1024);

	CustomVector temp(std::move(my_vec));	// R-value로 들어옴

	cout << my_vec.n_data << endl;	// my_vec의 값이 완전히 temp로 넘어가서 더 이상 존재 X
}

Move constructor를 통해 my_vec이 R-value로 temp에 넘어갔기 때문에 my_vec의 주소가 0으로 초기화되고 my_vec에 아무런 값이 남아있지 않음.


void doSomething(CustomVector& vec)	// L-value로 매개변수를 받아옴
{
	cout << "Pass by L-reference" << endl;
	CustomVector new_vec(vec);	// Copy-constructor
}

void doSomething(CustomVector&& vec)	// R-value로 매개변수를 받아옴
{
	cout << "Pass by R-value" << endl;
	CustomVector new_vec(std::move(vec));	// Move-constructor
}

int main()
{
	CustomVector my_vec(10, 1024);
	doSomething(my_vec);	// 이후에도 계속 사용할 계획 혹은 의지
	cout << endl;
	doSomething(CustomVector(10, 8));	// R-value로 doSomething을 호출
}

1. Constructor : my_vec(10, 1024) 초기화를 실행하면서 생성자 내부의 문자열 호출

2. doSomething(my_vec)을 실행하면서 L-value를 받는 doSomething 함수의 문자열 출력

3. doSomething 함수 내에서 전달인자로 받은 CustomVector 객체를 vec에 copy-construct하면서 출력

4. CustomeVector(10, 8)을 바로 생성하면서 생성자 내부의 문자열 호출

5. 4번에서 생성산 객체가 R-value로 전달되기 때문에 R-value를 받는 doSomething 함수가 실행되고 그 내부의 문자열 출력

6. doSomething 함수 내에서 std::move()를 통해 매개변수를 전달받아 move-construnct 생성자 내부의 문자열 출력

※ R-value 전달은 반드시 std::move를 사용해야 합니다


template<typename T>
void doSomethingTemplate(T vec)
{
	doSomething(vec);
}

int main()
{
	CustomVector my_vec(10, 1024);
	doSomethingTemplate(my_vec);
	doSomethingTemplate(CustomVector(10, 8));
}

클래스 역시 템플릿을 사용하면 컴파일러가 알아서 L-value 전달과 R-value 전달을 구분하지 못함


template<typename T>
void doSomethingTemplate(T&& vec)	// std::forward 사용
{
	doSomething(std::forward<T>(vec));
}

int main()
{
	CustomVector my_vec(10, 1024);
	doSomethingTemplate(my_vec);
	doSomethingTemplate(CustomVector(10, 8));	// R-value reference로 전달
}

std::forward()를 사용함으로써 컴파일러가 스스로 L-value와 R-value를 구분해서 전달