CodeNote
Coding Note
CodeNote
전체 방문자
오늘
어제
  • 전체 보기 (35)
    • C++ (33)
      • Modern C++ (12)
      • Modern C++ STL (0)
      • Thread (16)
      • Thread (Async) (5)
    • 디자인패턴 (0)
    • Algorithm (2)
    • Electron (0)
    • Python (0)

블로그 메뉴

  • 홈
  • Github
  • 태그
  • 방명록

공지사항

인기 글

태그

  • C++ #Memory
  • Free
  • C++
  • LOCK
  • 자료구조
  • mutex

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
CodeNote

Coding Note

[C++] Perfect forwarding
C++/Modern C++

[C++] Perfect forwarding

2022. 10. 5. 17:55

Perfect forwarding

퍼펙트 포워딩에 알아보기전에 타입 추론에 대한 내용부터 알아야한다.

template<typename T>
void PrintVar(T a)
{
	std::cout << typeid(a).name() << std::endl;
	std::cout << a << std::endl;
}

int main()
{
	int a = 1;
	PrintVar(a);
	
	double b = 1.1;
	PrintVar<int>(b);
}

위와 같은 템플릿 함수를 정의했을 때 PrintVar(a)는 타입 추론(Type deduction)에 의해 angle bracket(<>)을 쓰지 않아도 컴파일러가 알아서 컴파일을 해준다. 그런데 b를 보면 컴파일러가 알아서 지정해주는 것보다 직접 <>로 타입을 지정해주는 게 우선순위가 더 높다는 것을 알 수 있다.

 

foward reference (universial reference)

template<typename T>
void PrintVar(T& a)
{
	std::cout << typeid(a).name() << std::endl;
	std::cout << a << std::endl;
}

템플릿도 일반적인 Reference로 받는 것이 가능하다. RValue Reference를 받는 것 또한 가능한데 여기서 템플릿의 난해함이 발생하게 된다. 일반적인 함수에서 RValue Reference를 받는 함수에 LValue를 넘겨주게 되면 컴파일 에러가 발생한다.

 

이를 템플릿에서도 똑같이 만들어주고 호출할 수가 있는데,

template<typename T>
void PrintVar(T&& a)
{
	std::cout << typeid(a).name() << std::endl;
	std::cout << a << std::endl;
}
int main()
{
	PrintVar<int>(a); // <>로 지정해주면 컴파일 에러 발생
	PrintVar(a); // 타입 추론을 하라고 하면 빌드가 된다.
}

이렇듯 템플릿에서는 && 2개가 붙어 있으면 이는 RValue Reference가 아니라 Forward Reference 혹은 Universial Reference라고 부른다. 이는 LValue Reference 가 되기도하고 RValue Reference 가 되기도 하기 때문이다.

 

Compiler Explorer로 확인해보면

 

함수 PrintVar의 타입 T는 int& 이고 argument 역시 LValue Ref인 int&가 된다.

  • 타입 T : int&
  • Argument : int&

그런데 RValue로 넘겨주면 타입 T는 순수한 int이고 argument는 RValue의 Reference인 int&&가 된다.

  • 타입 T : int
  • Argument : int&&

즉, Forward Reference로 받았기 때문에 LValue Ref도 받을 수 있고 RValue Ref도 받을 수 있게 컴파일이 된 것이다.

 

std::forward

std::forward는 LValue와 RValue의 특성을 살려주는 역할을 한다.

 

Example Code

template<typename T>
void PrintVar(T&& a)
{
	std::string localVar{ std::move(a) }; // a를 localVar에서 사용하고 싶은 경우
	std::cout << a << std::endl;
}
int main()
{
	std::string a = "abcd";
	PrintVar(a);
	PrintVar(std::move(a));
}

a가 RValue Reference로 넘어오면 효율적인 사용을 위해 std::move()를 사용해야하는데 그렇게 되면 소유권이 localVar로 넘어가기 때문에 문제가 생길 수 있다.

 

즉,

  • PrintVar(a)의 경우
void PrintVar(string& a);

T = string& (string&) 와 같이 넘어가므로 std::move가 되면 a는 소유권을 잃어버린다.

 

  • PrintVar(std::move(a))의 경우 
void PrintVar(string&& a);

T = string (string&&)으로 넘어가는데 위에서 a가 소유권을 잃었으므로 아무것도 가르키지 않는다.

 

template<typename T>
void PrintVar(T&& a)
{
	std::string localVar{ std::forward<T>(a) }; // a를 localVar에서 사용하고 싶은 경우
	std::cout << a << std::endl;
}

그런데 여기서 std::foward를 사용하면 Reference의 특성을 그대로 살려준다. 즉, LValue Reference로 넘어오면 LValue Reference로 받아주어 a는 복사가 일어나게 되고,  RValue Reference로 넘어오면 RValue Reference를 받아주어 a는 move가 일어난다. 

'C++ > Modern C++' 카테고리의 다른 글

[C++] 가상함수 원리  (0) 2022.10.24
[C++] attributes(속성)  (0) 2022.10.13
[C++] constexpr  (0) 2022.10.05
스마트 포인터 문제 - 순환 참조  (0) 2022.10.03
[C++20] std::span  (0) 2022.10.01
    'C++/Modern C++' 카테고리의 다른 글
    • [C++] 가상함수 원리
    • [C++] attributes(속성)
    • [C++] constexpr
    • 스마트 포인터 문제 - 순환 참조
    CodeNote
    CodeNote
    기록 블로그

    티스토리툴바