SW 그리고 아빠 엔지니어링 중...

아는 만큼 보이고, 경험해봐야 알 수 있고, 자꾸 써야 내 것이 된다.

L C++/Concurrency

[Utilities] std::reference_wrapper (std::ref) : thread를 잘 사용하기 위해 필요한 템플릿 클래스

보리남편 김 주부 2023. 5. 28. 01:58
본 글은 std::reference_wrapper 의 용도와 목적에 대한 글이기에
std::reference_wrapper 사용방법을 알고싶다면 다른 글에서 확인 부탁 드립니다.

필요성


thread 로 동작시켜야 할 함수(or 클래스)가 있는데 thread 내에서 계산을 하고 그 값을 main thread를 받아야 할 때 '전역변수'로만 사용하고 있다면 std::reference_wrapper 를 알아두면 좋다.

 

활용 예시


다음 예제코드는 std::reference_wrapper 활용하여 원하는 n의 값을 얻는 예제코드이다.

  • main 함수에 n = 0 의 값을 foo 함수 내에서 처리된 10의 값을 얻고 싶다.

 

  • 코드 실행해보기 (아래 우측 상단 아이콘 선택 시 실행 가능)

 

  • 전체코드 한눈에 보기
더보기
#include <iostream>
#include <functional>

void foo(int& arg) { arg = 10; }

template<typename T>
void send_foo(T a)
{
	foo(a);
}

int main()
{
	int n = 0;

	//1. 직접 참조 사용 시 문제 없음
	foo(n);
	std::cout << "foo() : " << n << std::endl;
	
	n = 0;
	//2-1. 전달 함수를 통해 사용 시 
	send_foo(n); // 받으면서 복사본 생성
	std::cout << "send_foo() : " << n << std::endl;	
	
	//2-2. 포인터는 복사본은 아니지만 foo 대입 시 참조 변환 안됨 int* -> int (X) 컴파일 에러
//	send_foo(&n);

	//3-1. reference_wrapper : // n 을 참조로 전달하겠다는 의도.
	std::reference_wrapper<int> r(n);
	send_foo(r); 
	std::cout << "send_foo() : " << n << std::endl;	

	n = 0;
	//3-2. std::ref(n) : reference_wrapper 를 만들어주는 함수
	send_foo( std::ref(n) );		
	std::cout << "send_foo() : " << n << std::endl;	
	return 0;
}
결과:
foo() : 10
send_foo() : 0
send_foo() : 10
send_foo() : 10

case 1. 직접 참조의 경우는 10을 얻을 수 있다.

case 2. foo 함수가 send_foo 함수를 거쳐서 전달되는 경우

: send_foo 함수의 T a 의 값은 10이지만 a는 n의 참조 값이 아니기에 n은 10이 될 수 없다.

case 3. n 을 std::reference_wrapper로 전달하는 경우

: foo 에서 얻은 10의 값을 n이 가질수 있다. std::ref 는 reference_wrapper 를 만들어 주는 함수이다 내부적으로 3-1과 같이 std::reference_wrapper<int> r(n) 이 것이 수행된다.

 

C++ 표준 참조와 비교


C++ 표준 참조와 비교해 볼 수 있는 다음 예제코드를 통해 std::reference_wrapper 특성을 알아보자.

 

 

  • v1, v2 를 참조하는 C++ 표준 참조 r1, r2 에서 r1  = r2 의 결과를 std::reference_wrapper 로 참조한 ref_r1, ref_r2의 값의 결과를 우선 비교해보자.

 

  • 코드 실행해보기 (아래 우측 상단 아이콘 선택 시 실행 가능)

 

  • 전체코드 한눈에 보기
더보기
#include <iostream>
#include <functional>
// C++ 레퍼런스 : 값의 이동, 레퍼런스 자체는 이동될수 없다
// reference_wrapper : 이동가능한 참조
//					   대입연산시 참조가 이동
int main()
{
	int v1 = 10, v2 = 20;

	int& r1 = v1;
	int& r2 = v2;

	r1 = r2;

									        	// raw reference  
	std::cout << "v1 : " << v1 << std::endl; 		  // 20		
	std::cout << "v2 : " << v2 << std::endl; 		  // 20		
	std::cout << "r1 : " << r1 << std::endl; 		  // 20		
	std::cout << "r2 : " << r2 << std::endl;		  // 20		

	//init
	v1 = 10, v2 = 20;

	std::reference_wrapper<int> ref_r1 = v1;
	std::reference_wrapper<int> ref_r2 = v2;

	ref_r1 = ref_r2;

									        	// raw reference  reference_wrapper
	std::cout << "v1     : " << v1 << std::endl; 		  // 20				10
	std::cout << "v2     : " << v2 << std::endl; 		  // 20				20
	std::cout << "ref_r1 : " << ref_r1 << std::endl;  	  // 20				20
	std::cout << "ref_r2 : " << ref_r2 << std::endl;  	  // 20				20
}
//결과
v1 : 20
v2 : 20
r1 : 20
r2 : 20
v1     : 10
v2     : 20
ref_r1 : 20
ref_r2 : 20

 

위 코드에서 C++ 표준 참조는 r1는 v1을 참조하고, r2는 v2을 참조하기에 아래와 표현할 수 있다.

 

여기서 r1 =  r2 을 하게되면 이 값을 복사하여 아래와 같이 v2, r2의 값 20이 r1, v1의 값이 20이 된다.

 

하지만, 위 코드에서 std::reference_wrapper는 ref_r1는 v1의 포인터를 참조하고, ref_r2는 v2의 포인터를 참조하고 있기에 ref_r1 = ref_r2를 하게 되면

 

아래와 같이 ref_r1 의 참조가 ref_r2로 바뀌기에 v1의 참조는 끊어지고, v2를 참조하여 v1을 제외하고 모두 20을 값을 가지게 된다. 

 

위 예제코드를 통해 표준 C++ 참조와 특성을 비교해보면 다음과 같다.

C++ 참조 : 이동 불가능한 참조. "const" 이다. 대입연산시 '참조의 이동'이 아니라 '값의 이동'이다. (깊은 복사)
std::reference_wrapper : 참조가 변경이 가능한다. 대입 연산시 '참조의 이동'이 된다.
* 이 개념이 C++20의 "ref_view"로 발전

 

참고


std::reference_wrapper는 C++11 부터 지원되는 템플릿 클래스이며 <functional> 헤더에 정의되어 있다.

std::reference_wrapper 구현 내용을 보면, 객체를 가르키기위해서 참조 시 포인터를 가지고 있고, 기존 참조도 호환하기위해 operator T&() 가 있음을 확인 할 수 있다.

int& r3 = ref_r1; // 가능

https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper

 

std::reference_wrapper - cppreference.com

template< class T > class reference_wrapper; (since C++11) std::reference_wrapper is a class template that wraps a reference in a copyable, assignable object. It is frequently used as a mechanism to store references inside standard containers (like std::ve

en.cppreference.com

* 본 글에 예제 코드는 코드누리 교육을 받으면서 제공된 샘플코드를 활용하였습니다.

728x90