(Cโบโบ) ๐Ÿคšrvalue reference

๋„ˆ๋Š” ์™œ ํ˜ผ์ž ๋” ๋ณต์žกํ•ด์ง€๊ณ  ์žˆ๋‹ˆ(โ€ฆMove ๋•Œ๋ฌธ์—)

1 minute read

๋ง๊ทธ๋Œ€๋กœ rvalue์˜ reference์ด๋‹ค. ๋”ฐ๋ผ์„œ ์ผ๋ฐ˜๋ณ€์ˆ˜์™€ ๊ฐ™์€ lvalue๋Š” ์ฐธ์กฐํ•˜์ง€ ๋ชปํ•œ๋‹ค. (๋‚˜์ค‘ universal reference์™€๋Š” ๊ตฌ๋ถ„ํ•œ๋‹ค) ๋‹ค๋งŒ ์–ด๋ ค์›Œ์ง€๋Š” ๋ถ€๋ถ„์€ rvalue์˜ ์ •์˜๊ฐ€ Modern C++๋ถ€ํ„ฐ ๊ต‰์žฅํžˆ ๋ณต์žกํ•ด์กŒ๋‹ค๋Š” ์ ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ณ ์ „์ ์ธ rvalue๋Š” pure rvalue๋กœ์„œ ํ•˜๋‚˜์˜ ์˜์—ญ์— ๋ถˆ๊ณผํ•ด์กŒ๋‹ค.

๋ณธ์งˆ์ ์œผ๋กœ ์ด๋Ÿฐ ๋ณต์žกํ•œ ๊ฐœ๋…์€ Move ์—ฐ์‚ฐ์„ ์œ„ํ•ด ์กด์žฌํ•œ๋‹ค. ๊ทธ๊ฒƒ์€ Rust์ฒ˜๋Ÿผ ์†Œ์œ ๊ถŒ ๊ฐœ๋…์„ ํ™•์‹คํžˆ ํ•˜๊ธฐ ์œ„ํ•จ์ด๋‹ค. ๊ณต์œ  ๋ฉ”๋ชจ๋ฆฌ๋Š” ์žฌ์•™์˜ ์”จ์•—์ด๋‹ค. (ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋ณต์žกํ•˜๋ฉด ์ฐจ๋ผ๋ฆฌ Rust๋ฅผ ์“ฐ๋Š”๊ฒŒ ์–ด๋–จ๊นŒโ€ฆ)

๊ธฐ๋ณธ์ ์ธ ์‚ฌ์šฉ๋ก€๋ฅผ ๋ณด๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

#include <iostream>  
#include <algorithm>  
using namespace std;  
  
int&& foo() { return 10; }  
void f(int&) { cout << "int&" << endl; }  
void f(int&&) { cout << "int&&" << endl; }  
  
int main()  
{  
	int&& rr1 = 10;  
	int&& rr2 = foo();  
	f(10);   
	f(foo());   
	f(rr1);   
	f(rr2);   
  
	int n = 10;  
	f(n);   
	f(static_cast<int&&>(n));   
	f(move(n));   
	return 0;  
}  

&& ์—ฐ์‚ฐ์ž๋กœ rvalue reference๋ฅผ ์ •์˜ํ•œ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ๊ทธ ์ค‘ rvalue reference๋กœ ๋ณ€ํ™˜ํ•˜๋Š” move ํ•จ์ˆ˜์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๋ฉด,

template <typename T>  
T&& move(T t)  
{  
    return static_cast<T&&>(t);  
}  

์ด ์ •๋„๋กœ ๊ตฌํ˜„๋œ๋‹ค๊ณ  ์ถ”์ธกํ•  ์ˆ˜ ์žˆ๋‹ค. ์ฆ‰, ์œ„ ์˜ˆ์ œ์—์„œ move ํ•จ์ˆ˜ ์œ— ๋ผ์ธ๊ณผ ๋™์ผํ•œ ๊ฒƒ์ด๋‹ค. move๋ผ๋Š” ํ•จ์ˆ˜์˜ ์ด๋ฆ„์€ ๊ด€๋ จ์žˆ๋Š” ๊ฐœ๋…์ธ ์ด๋™ ์ƒ์„ฑ์ž์™€ ์—ฐ๊ด€์ด ์žˆ๋Š”๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

์œ„์—์„œ๋„ ์ž ๊น ์–ธ๊ธ‰ํ–ˆ๋˜ universal reference์— ๋Œ€ํ•ด ์‚ดํŽด๋ณด๋ฉด, auto๋‚˜ template ์ธ์ž์ฒ˜๋Ÿผ type์„ ์ถ”๋ก ํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” && ์—ฐ์‚ฐ์ž๊ฐ€ lvalue์™€ rvalue๋ฅผ ๋ชจ๋‘ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

int n = 10;  
auto&& rRef = n;  

์œ„์™€ ๊ฐ™์ด lvalue๋ฅผ ๋ฐ›๋Š”๋‹ค๋˜์ง€, ์•„๋ž˜ ์˜ˆ์ œ์—์„œ ์ฃผ์„์˜ arg์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ด ๊ฐ€๋Šฅํ•œ ๊ฒƒ์ด๋‹ค.

#include <iostream>  
#include <mutex>  
using namespace std;  
  
void f1(int a) { cout << "f1 : " << a << endl; }  
void f2(int& a) { cout << "f2 : " << a << endl; a = 10; }  
void f3(int&& a) { cout << "f3 : " << a << endl; }  
mutex m;  
  
// arg๋Š” lvalue, rvalue๊ฐ€ ๋ชจ๋‘ ๊ฐ€๋Šฅํ•˜๋‹ค  
template<typename F, typename A> void lockAndCall(F func, A&& arg)  
{  
	lock_guard<mutex> lock(m);  
	func(forward<A&&>(arg));    // perfect forwarding  
}  
int main()  
{  
	int n = 0;  
	lockAndCall(f1, 10);   
	lockAndCall(f2, n);   
	lockAndCall(f3, 10);   
	return 0;  
}  

์œ„ ์˜ˆ์ œ๋Š” perfect forwarding์— ๋Œ€ํ•œ ์˜ˆ์ œ์ด๊ธฐ๋„ ํ•œ๋ฐ, ์ธ์ž๋กœ rvalue reference๋ฅผ ๋ฐ›๋Š” ํ•จ์ˆ˜์—์„œ ๋ฐ›์€ ์ธ์ž๋ฅผ ๋‹ค๋ฅธ ํ•จ์ˆ˜์—๊ฒŒ rvalue๋กœ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

template<typename F, typename A> void lockAndCall(F func, A&& arg)  
{  
	lock_guard<mutex> lock(m);  
	func(arg);    // why not?  
}  

์ด๋ฏธ arg๊ฐ€ A&& ํƒ€์ž…์ธ๋ฐ func์—๊ฒŒ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌํ•˜๋ฉด ์•ˆ๋˜๋‚˜? ๋ผ๊ณ  ์ƒ๊ฐํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ๊ธฐ์–ตํ•ด์•ผ ํ•œ๋‹ค.

์ด๋ฆ„์ด ์žˆ๋Š” rvalue reference๋Š” lvalue์ด๋‹ค.
arg๋Š” ์ด๋ฆ„์ด ์žˆ๋Š” ๋งค๊ฐœ๋ณ€์ˆ˜์ด๊ธฐ ๋•Œ๋ฌธ์— lvalue๋กœ ์ธ์‹๋œ๋‹ค.