Notice
Recent Posts
Recent Comments
Link
- 책_곽용재님 홈페이지
- 책_노란북 - 책 가격비교
- 책_김재우-SICP번역
- 플밍_쏘쓰포지
- 플밍_CodingHorror ?
- 플밍_상킴
- 플밍_김민장님
- GPGStudy
- 플밍_미친감자님
- 플밍_jz
- 플밍_샤방샤방님
- 플밍_글쓰는프로그래머2
- 플밍_키보드후킹
- 사람_재혁
- 사람_kernel0
- 사람_박PD
- 사람_경석형
- 사람_nemo
- 사람_kikiwaka
- 사람_Junios
- 사람_harry
- 사람_어떤 개발자의 금서목록..
- 사람_모기소리
- 사람_낙타한마리
- 사람_redkuma
- 사람_영원의끝
- 사람_민식형
- 도스박스 다음카페
- 플레이웨어즈 - 게임하드웨어벤치마크
- http://puwazaza.com/
- David harvey의 Reading Marx's c…
- 씨네21
- 한겨레_임경선의 이기적인 상담실
- 본격2차대전만화 - 굽시니스트
- 영화_정성일 글모음 페이지
- 영화_영화속이데올로기파악하기
- 음식_생선회
- 죽력고
- 사람_한밀
- 플밍_수까락
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 삼국지
- 진삼국무쌍5
- 정신분석
- 책
- stl
- 고등학교 사회공부
- 일리아스
- 유머
- 게임
- template
- modernc++
- 진중권
- 인문학
- programming challenges
- 단상
- 건강
- 태그가 아깝다
- 고전강의
- 영화
- 프로그래밍
- BSP
- 김두식
- 강유원
- 삼국지6
- 유시민
- 노무현
- 정성일
- c++
- Programming
- 소비자고발
Archives
- Today
- Total
01-05 01:43
lancelot.com
rvalue reference 본문
- lvalue vs rvalue
- "표현식( expression )" 이 등호의 왼쪽에 놓일 수 있으면 lvlaue, 놓일 수 없으면 rvalue
- 각 언어마다 "정의가 약간씩 다르다."
- C++ 에서의 특징
lvalue | rvalue |
등호( = ) 의 왼쪽에 올 수 있다 | 등호( = )의 왼쪽에 올 수 없다 |
이름이 있고, 단일식을 벗어나서 사용가능 | 이름이 없고, 단일 식에서만 사용 |
주소 연산자로 주소를 구할 수 있다 | 주소 연산자로 주소를 구할 수 없다 |
참조를 반환하는 함수 문자열 literal |
값을 반환하는 함수 실수/정수 literal 임시객체( temporary ) |
- 상수는 rvalue 이다? -> No
- 모든 rvalue는 상수이다? -> No
#include<iostream>
int x = 10;
int f1() { return x; } // "10"을 반환
int& f2() { return x; } // x의 별명을 반환
int main()
{
int v1 = 0, v2 = 0;
v1 = 10; // v1 : lvalue
// 10 = v1; // error 10 : rvalue
v2 = v1;
int* p1 = &v1; // ok
// int* p2 = &10; // error
// f1() = 20; // 10=20 error
f2() = 20; // x=20 ok
const int c = 10;
// c = 20; // error
// immutable lvalue
// 10 = 20; // error. 10은 lvalue가 아니다.
// "aa"[0] = 'x'; // error. lvalue 문제가 아니라 const char[3] 이므로
std::cout << "aa"[1] << std::endl;
int n = 3;
n = 10;
// n + 2 = 10; error
// n + 2 * 3 = 10; error
(n = 20) = 10; // n은 reference
++n = 10; // n은 reference
// n++ = 10; n은 값
}
- lvalue, rvalue
- 객체, 변수에 부여되는 속성이 아닌, "표현식( expression )" 에 부여되는 속성.
- 표현식( expression )
- "하나의 값"을 만들어내는 코드 집합
- "a sequence of operators and operands that specifies a computation"
- 표현식이 lvalue 인지 rvalue 인지 조사하는 방법
- "decltype( expression ) 의 결과로 나오는 타입을 확인" 하면된다
- decltype( expression ) 의 추론규칙
- "expression이 등호의 왼쪽에 올 수 있다면 lvalue reference" 타입
- 단, expression 에 "이름"만 있다면 선언을 보고 결정. - "이름"을 ( ) 로 묶으면 된다.
#include<iostream>
#include<type_traits>
#define value_category(...) \
if(std::is_lvalue_reference_v<decltype((__VA_ARGS__))> ) \
std::cout<<"lvalue"<<std::endl; \
else if(std::is_rvalue_reference_v<decltype((__VA_ARGS__))> ) \
std::cout<<"rvalue(xvalue)"<<std::endl; \
else \
std::cout << "rvalue(pvalue)" << std::endl;
int main()
{
int n = 10;
value_category(n);
value_category(n+2);
value_category(++n);
value_category(n++);
value_category(10);
value_category("AA");
}
- reference 의 종류
- lvalue reference ( int& )
- rvalue reference ( int&& )
- 규칙 1.
- non-const lvalue reference 는 lavalue만 가리킬 수 있다
- 규칙 2.
- const lvalue reference 는 lvalue와 rvalue를 모두 가리킬 수 있다.
- 규칙 3. C++11부터
- rvalue reference 는 rvalue만 가리킬 수 있다.
- 왜 상수성 없이 rvalue를 가리키는 것이 중요한가?
- move sementic 와 perfect forwarding을 위해서
int main()
{
int n = 3;
int& r1 = n;
// int& r2 = 3; //error
const int& r3 = n; // ok
const int& r4 = 3; // ok
// int&& r5 = n; // error
int&& r6 = 3; // ok
}
- reference 와 overloading
- overloading 규칙
- 값 타입과 참조 타입은 오버로딩 될 수 없다.
- 참조 타입끼리는 오버로딩 될 수 없다.
- reference 인자의 의도
- overloading 규칙
foo( X& x ) | out parameter 객체를 수정하겠다는 의미 lvalue만 받을 수 있다. |
foo( const X& x ) | in parameter 객체를 읽기만 하겠다는 의미 lvalue 과 rvalue를 모두 받을 수 있다. |
foo( X&& x ) | move sementic 을 사용하겠다는 의도 rvalue 만 받을 수 있다. |
foo( const X&& x ) | rvalue 만 받을 수 있다. 문법적으로 만들 수 있지만, 의미가 없다. 현재 C++에서는 사용되지 않음. |
#include<iostream>
class X {};
//void foo(X x) { std::cout << "X" << std::endl; }
void foo(X& x) { std::cout << "X&" << std::endl; } // 1
void foo(const X& x) { std::cout << "const X&" << std::endl; } // 2
void foo( X&& x) { std::cout << "X&&" << std::endl; } // 3
//void foo(const X&& x) { std::cout << "const X&&" << std::endl; }
int main()
{
X x;
foo(x); // lvalue
// 1번 호출, 없으면 2번
foo(X()); // rvalue
// 3번 호출, 없으면 2번
}
표현식 | 타입 | value category |
X() | X | rvalue |
rx | X&& | lvalue |
#include<iostream>
class X {};
void foo(X& x) { std::cout << "X&" << std::endl; } // 1
void foo(const X& x) { std::cout << "const X&" << std::endl; } // 2
void foo( X&& x) { std::cout << "X&&" << std::endl; } // 3
int main()
{
foo(X()); // 3번
X&& rx = X();
foo(rx); // 1번
// lvalue => rvalue 캐스팅하면 3번
foo(static_cast<X&&>(rx)); // 3번
}
- foo( X&& )
- rvalue reference 를 받는 것이 아니라 rvalue를 받겠다는 의미
- static_cast<X&&>(rx);
- rx가 이미 X&& 타입인데, "같은 타입 캐스팅" 아닌가요? -> 아님
- 예외적으로 이 표기볍은 "타입 캐스팅이 아닌 value를 변환 하는 캐스팅"
- reference collapsing
- 참조를 가리키는 참조 타입
- 참조를 가리키는 참조 변수를 "직접 코드"로 만들 수 없다.
- 하지만, type deduction 과정에서 참조를 가리키는 참조 타입이 발생하면 "reference collapsing 규칙에 따라 타입이 결정"된다.
- reference collapsing 규칙
- Type& & : Type&
- Type& && : Type&
- Type&& & : Type&
- Type&& && : Type&&
- 참조를 가리키는 참조 타입
int main()
{
int n = 3;
int& lr = n; // lvalue reference
int&& rr = 3; // rvalue reference
// int&& ref2ref = lr; // error
decltype(lr)& r1 = n; // int& & => int&
decltype(lr)&& r2 = n; // int& && => int&
decltype(rr)& r3 = n; // int&& & => int&
decltype(rr)&& r4 = 3; // int&& && => int&&
}
- "reference collapsing" 이 적용되는 경우
- typedef
- using
- decltype
- template
template<typename T> void foo(T&& arg)
{
}
int main()
{
int n = 10;
typedef int& LREF;
LREF&& r1 = n; // int& && => int&
using RREF = int&&;
RREF&& r2 = 10; // int&& && => int&&
decltype(r2) && r3 = 10; // int&& && => int&&
foo<int&>(n); // foo(int& && arg)
// foo(int& arg) 의 함수 생성
}
- forwarding ( universal ) reference
- 함수 파라미터의 모양
- int& : int 타입의 lvaule 만 전달할 수 있다.
- int&& : int 타입의 rvalue 만 전달할 수 있다.
- T& : ?
- T&& : ?
- 함수 파라미터의 모양
void f1(int& arg) {}
void f2(int&& arg) {}
template<typename T> void f3(T& arg) {}
int main()
{
int n = 0;
f1(n); // ok
// f1(0); // error
// f2(n); // error
f2(0); // ok
f3(n); // ok
// f3(0); // error
}
- template 에서의 forward(universal ) reference
- 사용자가 "템플릿 인자를 직접전달" 하는 경우
- 사용자가 전달한 타입을 적용해서 함수가 생성된다
- 사용자가 "템플릿 인자를 직접전달" 하는 경우
전달한타입(T) | T& | 최종 생성된 함수 |
int | int & => int& | f3( int& arg ) |
int& | int& & => int& | f3( int& arg ) |
int&& | int&& & => int& | f3( int& arg ) |
- 사용자가 "템플릿 인자를 직접 전달하지 않는" 경우
- 함수의 인자를 받을 수 있도록 T의 타입을 결정한다
f3( 0 ) | T를 int, int&, int&& 중 어떠한 것으로 결정을 해도 rvalue인 0을 받을 수 없다. 컴파일러는 T를 int로 결정하고 함수를 생성하지만 "compile error" |
f3( n ) | T를 int, int&, int&& 중 어떠한 것으로 결정을 해도 lvalue인 n을 받을 수 있다. 컴파일러는 T를 int로 결정 |
template<typename T> void f3(T& arg) {}
int main()
{
int n = 0;
// 1. 사용자가 템플릿 인자를 직접 전달하는 경우
f3<int>(n); // T : int T& : int& f3( itn& arg ) 함수 생성
f3<int&>(n); // T : int& T& : int& & f3( itn& arg ) 함수 생성
f3<int&&>(n); // T : int&& T& : int&& & f3( itn& arg ) 함수 생성
// 2. 사용자가 템플릿 인자를 전달하지 않은 경우.
// f3(0); // error
f3(n); // ok
}
- 함수 파라미터의 모양
int& | "int 타입의 lvlaue"만 전달할 수 있다 |
int&& | "int 타입의 rvlaue"만 전달할 수 있다 |
T& | "임의의 타입의 lvlaue"만 전달할 수 있다 |
T&& | ? |
- T&&인 템플릿 함수의 경우
template<typename T> void f4(T&& arg) {}
int main()
{
int n = 0;
// 1. 사용자가 템플릿 인자를 직접 전달하는 경우
f4<int>(0); // T : int T&& : int&& f4( itn&& arg ) 함수 생성
f4<int&>(n); // T : int& T&& : int& && f4( itn& arg ) 함수 생성
f4<int&&>(0); // T : int&& T&& : int&& && f4( itn&& arg ) 함수 생성
// 2. 사용자가 템플릿 인자를 전달하지 않은 경우.
f4(0); // T=int& f4(int& arg)
f4(0); // T=int&& f4(int&& arg)
}
- 사용자가 "템플릿 인자를 직접전달" 하는 경우
- 사용자가 전달한 타입을 적용해서 함수가 생성된다
전달한타입(T) | T&& | 최종 생성된 함수 |
int | int && => int&& | f4( int&& arg ) |
int& | int& && => int& | f4( int& arg ) |
int&& | int&& && => int&& | f4( int&& arg ) |
- 사용자가 "템플릿 인자를 직접 전달하지 않는" 경우
- 함수의 인자를 받을 수 있도록 T의 타입을 결정한다
f4( n ) | T를 int& 로 결정해야만 n을 받을 수 있다. T=int& 로 결정, 최종함수 f4( int& arg ) T=int 로 결정하면 n을 받을 수 없다. |
f4( 0 ) | T를 int 또는 int&& 로 결정하면 rvalue인 0을 받을 수 있다. T=int 로 결정, 최종함수 f4( int&& arg ) |
- 함수 파라미터의 모양
int& | "int 타입의 lvlaue"만 전달할 수 있다 | lvalue reference |
int&& | "int 타입의 rvlaue"만 전달할 수 있다 | rvalue reference |
T& | "임의의 타입의 lvlaue"만 전달할 수 있다 | lvalue reference |
T&& | "임의의 타입의 lvalue와 rvalue" 를 모두 전달할 수 있다. | forwarding reference : 표준위원회 universal reference : 스캇 마이어스 |
- "모두 전달할 수 있다" 의 정확한 의미는?
T | T&& | 최종 생성된 함수 | |
f4( n ) | int& | int& | f4( int& arg ) |
f4( 0 ) | int | int&& | f4( int&& arg ) |
- lvalue를 받을 수 있는 "함수가 생성"될 수 있고, rvalue를 받을 수 있는 "함수가 생성"될 수 있다는 의미.
#include<iostream>
template<typename T> void f4(T&& arg)
{
std::cout << __FUNCSIG__ << std::endl;
}
int main()
{
int n = 0;
f4(n); // T=int& f4(int& arg)
f4(0); // T=int&& f4(int&& arg)
const int c = 0;
f4(c);
}
- 함수 템플릿을 만들 때 "forwarding reference"를 사용하면
- lvalue와 rvalue를 각각 받을 수 있는 "함수 생성"
- 생성된 각 함수는 "call by value 가 아닌 reference"를 사용해서 전달 받는다.
- lvalue와 rvalue를 모두 받을 수 있는 함수 만들기
- 방법 1. call by value
- 인자로 전달된 객체의 "복사본이 생성" 된다.
- 방법 2. const lvalue reference
- 복사본은 생성되지 않지만 "const 속성을 추가"해서 가리킨다.
- 방법 3. lvalue 버전과 rvalue 버전의 함수를 따로 제공
- 복사본도 없고, const 속성도 추가되지 않는다
- 함수 "인자로 전달된 객체를 속성의 변화 없이" 받을 수 있다.
- 방법 4. forwarding reference 사용
- 방법 3의 함수들을 "컴파일러가 자동 생성"
- 방법 1. call by value
void foo1(int arg) {}
void foo2(const int& arg) {}
void foo3(int& arg) {}
void foo3(int&& arg) {}
template<typename T> void foo4(T&& arg) {}
int main()
{
int n = 0;
foo4(n);
foo4(0);
}
- forwarding reference 의 의미
- 임의 타입의 lvalue와 rvalue를 복사본을 만들지 않고 속성의 변화없이 그대로 받고싶을 때 사용
- forwarding reference가 활용되는 주된 분야
- Move sementic
- Perfect forwarding
- forwarding reference 사용시의 주의사항
- forwarding reference 를 사용하려면
- 함수 자체가 템플릿이어야한다.
- foo함수는 "함수 템플릿이 아니라 클래스 템플릿의 메멉 함수(템플릿이 아닌)"이다.
- forwarding reference 를 사용하려면
template<typename T>
class Test
{
public :
void foo(T&& arg) {}
template<typename U> void goo(U&& arg) {}
};
int main()
{
int n = 0;
Test<int> t; // T=int
// void foo(int&& arg)
// 아래 2줄을 생각해봅시다.
// t.foo(n); // error
t.foo(0); // ok
t.goo(n);
t.goo(0);
}
- auto 와 forwarding reference
- auto는 template과 type deduction 규칙이 동일하다.
auto& | lvalue reference |
auto&& | forwarding reference |
int main()
{
int n = 0;
auto a1 = n; // ok
auto a2 = 0; // ok
auto& a3 = n; // ok
// auto& a4 = 0; // error
// T&& arg = 함수인자 를 적용하면 됨
auto&& a5 = n; // auto=int& int& && a5=n
auto&& a6 = 0; // auto=int int && a6=0;
}
- auto&&
auto&& a5 = n | auto는 int& 로 결정됨 int& && a5 = n => int& a5 = n |
auto&& a6 = 0 | auto는 int 로 결정됨 int && a6 = n => int&& a6 = 0 |
- auto 자리는 T로, 우변을 함수 인자로 생각