C++ 중급 - Perfect Forwarding

타입·2024년 2월 23일
0

C++ 공부

목록 보기
15/17

완벽한 전달 (Perfect Forwarding)

  • perfect forwarding
    전달받은 인자를 다른 함수에게 값, const/volatile 속성, value category 등의 변화없이 그대로 전달

  • perfect forwarding을 하려면

    • 함수 인자를 받을 때 복사본이 아닌 reference로 받아야함
    • int&, int&&을 각각 제공해야함, const 버전도 필요
    • int&& 버전에서는 인자를 전달할 때 rvalue로 캐스팅해서 전달
#include <iostream>

void hoo(int&& r) {}

// 함수가 수행되는데 걸린 시간을 구하는 함수 템플릿
// 전달받은 인자를 원래 함수에 전달해야함
template<class F>
void chronometry(F f, int& arg)
{
	f(arg);
}
template<class F>
void chronometry(F f, int&& arg)
{
//	f(arg); // arg라는 이름이 있으므로 lvalue가 되어버림
	f(static_cast<int&&>(arg)); // rvalue로 다시 캐스팅
}

int main()
{
	hoo(10); // ok
	chronometry(hoo, 10);
}

forwarding reference(T&&)를 사용하면

int&, int&&을 자동 생성할 수 있는데
템플릿을 사용하려면 구현이 동일해야함

void goo(int&  n) { n = 20;}
void hoo(int&& r) {}

template<class F, class T>
void chronometry(F f, T&& arg)
{
//	f(static_cast<T&&>(arg));
	f(std::forward<T>(arg));
}

int main()
{	
	int n = 0;
	chronometry(goo, n);
	chronometry(hoo, 10);

	std::cout << n << std::endl; // 20
}

lvalue를 (함수로 전달하면) lvalue로 캐스팅하고
rvalue를 (함수로 전달하면 lvalue로 변경되었던 걸 다시) rvalue로 캐스팅
인자가 rvalue인 경우만 std::move()를 하는 것!

  • perfect forwarding을 하려면
    • 인자를 받을 때 forwarding reference(T&&) 사용
    • 인자를 다른 함수에 전달할 때 std::forward<T>(arg)로 묶어서 전달
    • 여러 개의 인자를 모두 forwarding 하기 위해 가변인자 템플릿 사용
    • 반환 값도 전달하기 위해서는 decltype(auto) 반환
void foo() {}

int& goo(int a, int& b, int&& c)
{
	b = 20;
	return b;
}
template<class F, class ... T>
decltype(auto) chronometry(F f, T&& ... arg)
{
	return f(std::forward<T>(arg)...);
}

int main()
{	
	int n = 0;

	int& ret = chronometry(goo, 10, n, 10);
	chronometry(foo);
	ret = 100;
	
	std::cout << n << std::endl; // 100
}

일반함수와 멤버함수도 지원하기

std::invoke()를 사용하여 멤버 함수 포인터도 동작되도록 수정

#include <functional>

void foo(int n) {}

class Test
{
public:
	void f1(int n) { std::cout << "Test f1" << std::endl; }
};

template<class F, class ... T>
decltype(auto) chronometry(F f, T&& ... arg)
{
//	return f(std::forward<T>(arg)...);
	return std::invoke(f, std::forward<T>(arg)...);
}

int main()
{	
	chronometry(foo, 10);

	Test obj;
	chronometry(&Test::f1, &obj, 10);
}
  • 함수 객체 (Function Object)
    () 연산자를 재정의해서 함수처럼 사용가능한 객체
    람다 표현식의 원리가 함수 객체
    • lvalue 객체와 ravlue 객체를 구별할 수 있어야함
      f도 forwarding reference(&&)로 전달받고 std::forward<F>로 묶어서 전달
#include <functional>

struct Functor
{
	void operator()(int n) &
	{
		std::cout << "operator() &" << std::endl;
	}
	void operator()(int n) &&
	{
		std::cout << "operator() &&" << std::endl;
	}
};

template<class F, class ... T>
decltype(auto) chronometry(F&& f, T&& ... arg)
{
	return std::invoke(std::forward<F>(f),
				       std::forward<T>(arg)...);
}

int main()
{	
	Functor f;
	f(10); // f.operator()(10) // &
	Functor()(10); // &&
	chronometry(f, 10); // &
	chronometry(Functor(), 10); // &&
}

perfect forwarding 주의 사항

  1. 인자가 포인터인 경우 0 대신 nullptr을 사용
#include <functional>

template<class F, class ... T>
decltype(auto) chronometry(F&& f, T&& ... arg)
{
	return std::invoke(std::forward<F>(f), std::forward<T>(arg)...);
}

void foo(int* p) { }

int main()
{	
	foo(0);	// ok, 0은 포인터로 암시적 형변환 가능

//	int arg = 0;
//	foo(arg); // error, int 변수는 포인터로 암시적 형변환 불가능

 	// int&& arg = 0로 들어가 암시적 형변환 불가능
//	chronometry(foo, 0); // error
    
	// std::nullptr_t arg = nullptr로 들어감
    // nullptr은 모든 종류의 포인터로 암시적 형변환 가능
	chronometry(foo, nullptr); // ok
}
  1. 템플릿 전달 시 타입 결정, 오버로딩 함수의 ambiguous 문제
#include <functional>

template<class F, class ... T>
decltype(auto) chronometry(F&& f, T&& ... arg) 
{
	return std::invoke(std::forward<F>(f), std::forward<T>(arg)...);
}

void foo(std::pair<int, int> p)  {}

void goo(int a)        {}
void goo(int a, int b) {}

int main()
{	
//	foo({1,2}); // ok
//	chronometry(foo, {1,2}); // error, 템플릿으로 받아 전달할 수 없음 (foo()에 바로 보낼 땐 정확한 타입이 존재)
	chronometry(foo, std::pair{1,2}); // ok

	goo(1);
	goo(1,2);

//	chronometry(goo, 1, 2); // error, 어느 함수인지 판단할 수 없는 ambiguous 문제 (함수의 주소를 보내기 전 어느 함수인지 결정되어야 함)
	chronometry(static_cast<void(*)(int, int)>(goo), 1, 2); // ok
}
profile
주니어 언리얼 프로그래머

0개의 댓글