이 게시글은 계속 업데이트되고 있습니다. [최신 업데이트 12/18 ]
⭐️C++의 value category
C++의 표현식(expression)들은 type과 value category라는 두 개의 독립적인 속성(property)을 통해 특징 지을 수 있다.
C++ 11이 나오기전, C++ 03까지는 l-value와 r-value를 각각 assign operator(operator =)의 왼쪽에 올 수 있는 값, 오른쪽에 올 수 있는 값으로 정의 내렸었다.
이후 C++ 표준 개정안이 변경되면서 C++ 11부터는 value category에 x-value, gl-value, pr-value가 추가되었다.
value category는 두 가지의 기준 identity과 move를 통해 밑의 표와 같이 구분할 수 있다.
표에서 기본적인 분류 단위는 "Primary category"라고 하며, 두 개 이상의 복합 분류를 "Mixed category"라고 한다.
Primary category
- x-value (im)
- l-value (iM)
- pr-value (Im)
Mixed category
- gl-value ( i ) : x-value + l-value
- r-value ( m ) : x-value + pr-value
⭐️ value category의 구분 기준 : identity, move
value category를 구분하는 기준은 두 가지가 있다.
하나는 identity를 가지고 있는가? 이고 또 다른 하나는 move가 가능한가? 이다.
1. identity
identity는 기존의 l-value가 가지는 특정을 말하며, 두 객체(entity)의 주소를 비교해서 같은 값을 가리키고 있는지 비교할수 있는지의 여부를 말한다.
즉, 메모리를 점유하고 있는지(주소 값이 있는지)의 여부를 말한다.
예를 들어 a라는 변수와 b라는 변수가 있다고 가정하자. 이 때, 이 둘의 주소 값을 비교하여 같은 객체(entity)인지 확인할 수 있다. 따라서 두 변수는 identity를 가진다.
반면, 숫자 리터럴(Literal)이나 boolean 리터럴, nullptr은 주소 값을 비교하여 같은 객체(entity)인지 확인할 수가 없다.
메모리를 점유하여 주소 값을 갖고 있는 상태가 아니기 때문이다. 따라서 이들은 identity를 가지고 있지 않다.
identity를 가지고 있으면 "i"로 표현하며, 가지고 있지 않으면 "I"로 표현한다.
"identity를 가진다"라는 것은 표현식(expression)을 실행한 이후에도 객체(entity)가 존재하기 때문에, "persistence(지속성)가 있다"라고도 표현한다.
2. move
C++ 11에서는 identity 외에 한 가지 특성이 더 추가됐다. 바로 std::move()이다.
C++ 11부터 제공하는 move 함수는 <utility> 라이브러리에 있으며, 인자로 받은 객체를 r-value로 변환하여 리턴해준다.
std::move 함수는 인자로 받은 객체를 r-value로 변환하여 리턴해준다.
이름만 보면 무엇인가를 이동시킬 것 같지만 실제로는 타입 변환(casting)만 수행하며, 이동 생성자를 호출하게 된다.
move가 가능하다는 이야기는 어떤 값이 다른 값으로 이동(값이 메모리에서 이동될 수 있음)될 수 있다는 이야기이다.
move가 가능하면 "m"으로 표현하며, move가 가능하지 않으면 "M"으로 표현한다.
두 조건을 조합하면 im, Im, iM, IM 이렇게 네 가지가 나오는데, IM은 identity도 없고 move도 불가능하므로 프로그래밍적으로 의미가 없다.
먼저 value category를 im, Im, iM 세 가지로 나눌 수 있다.
im은 x-value, Im은 pr-value, iM은 l-value이며, 이 세 가지는 Primary category(기본 카테고리)에 속한다.
또한 i(identity를 지님)과 m(move가 가능)으로 나눌 수 있다.
i는 gl-value, m은 r-value이며, 이 두 가지는 Mixed category(복합 카테고리)에 속한다.
⭐️ Primary category
Primary category(기본 카테고리)에는 세 가지가 있다. 하나씩 살펴보자.
1. l-value (iM - identity 가짐, move 불가)
an lvalue (so-called, historically, because lvalues could appear on the left-hand side of an assignment expression) is a glvalue that is not an xvalue;
l-value (assignment expression:할당 표현식 의 왼편에 나타날 수 있었기 때문에 l-value라고 불렀습니다.)는 x-value가 아닌 gl-value입니다. -cpp reference
left-value의 약자이며, identity를 가지지만 move 될 수 없는 값을 말한다.
즉, 메모리 주소를 통해 두 객체(entity)가 같은 객체(entity)인지 확인할 수 있는 것을 말한다.
C++ 03이전에는 l-value를 assign operator(operator =)의 왼편에 사용할 수 있었기 때문에 l-value라고 불렀다.
[특징]
1. 수정 가능한 l-value는 assign operator(operator =)와 compound assignment operators(+=, -= 등)의 피 연산자로 사용이 가능하다.
2. & 연산자를 통해 특정 변수의 주소를 받아올 수 있다. (l-value만 & operator의 피 연산자로 사용 가능)
&std::endl 또한 가능한 표현식이다.
3. 표현식(expression)이 끝나더라도 메모리 공간을 점유하고 있다. (값이 살아있다.)
4. l-value reference를 초기화 하기 위해 l-value를 사용할 수 있다.
[예시]
1. 변수 혹은 함수의 이름, 멤버 변수, std::cin과 std::endl,
변수 type이 r-value reference여도, 변수의 이름으로 구성된 표현식(expression)은 l-value이다.
2. 함수 호출 or 반환 형식이 l-value reference인 오버 로딩된 연산자 expression 또한 l-value이다.
ex) getline(cin, str), cout << 1, str1 = str2, ++iter
static int num = 1;
int& function(){
num++;
return num;
}
int main(){
int a = 10, c = 10;
int b = a; // 할당 연산자(operator =)의 피연산자. b는 l-value
int *ptr = a; // 간접 접근 연산자 *, &, ->
++a; // 전위 증감 연산자에서의 피연산자. a는 l-value
a += b; // 복합 대입 연산자의 피연산자
string str = "Hello World!"; // string 리터럴(literal)
int *func = &function(); // operator &를 이용해서 주소 값을 받을 수 있으므로 l-value
a ? b : c; // a ? b : c의 반환값은 b 또는 c이므로 l-value
}
2. x-value (im - identity 가짐, move 가능)
an xvalue (an “eXpiring” value) is a glvalue that denotes an object whose resources can be reused.
x-value ("eXpiring:만료되는" value)는 다시 사용될 수 있는 리소스인 객체를 나타내는 gl-value이다.
eXpiring value의 약자인 x-value이다. 즉, 만료되어 가는 값이다.
표현식(expression)이 끝나고, 표현식이 의미하던 주소로 다시 접근했을 때, 값이 존재할 수도 있고 존재하지 않을 수도 있다. (move가 가능하기 때문)
메모리에 올라가는 순간 메모리 주소 값을 갖게 되기 때문에 pr-value가 될 순 없다.
컴파일러가 pr-value의 임시 데이터를 저장할 공간이 필요한데, 이 때 사용하는 임시 데이터 객체가 x-value이다.
std::move의 경우 매개변수로 전달받은 객체(l-value)를 r-value reference로 변환해주는데, 이 때 move한 값은 x-value에 속하게 된다.
[특징]
1. 컴파일러만 사용하는 객체이기 때문에 operator &의 피연산자로 사용할 수 없다.
2. 표현식(expression)이 끝났을 때 사라진다.
3. pr-value (Im - identity 없음, move 가능)
◽ a prvalue (“pure” rvalue) is an expression whose evaluation
◽ computes the value of an operand of a built-in operator (such prvalue has no result object), or
◽ initializes an object (such prvalue is said to have a result object).
◽ pr-value ("pure" r-value)는 operator(연산자)의 피연산자의 값을 계산하거나,
◽ 객체(object)를 초기화(이러한 pr-value는 result object를 가져야 한다) 하는 표현식을 말한다.
pr-value는 pure r-value의 줄임말로써, C++에서 유일하게 identity를 가지지 않는다.
후위 증감 연산자, 문자열을 제외한 모든 리터럴이 여기에 속한다.
reference를 반환하지 않는 함수의 실행결과, non-reference로의 타입 캐스팅(type casting), 숫자 & boolean 리터럴(literal)이 여기에 속한다.
[특징]
1. assign operator(operator =)의 우측에 올 수 있다.
2. 메모리 공간을 점유하고 있지 않고, 따라서 주소 값이 없다.
⭐️ Mixed category
Mixed category(복합 카테고리)는 두 가지로 나눌 수 있다.
1. gl-value (i)
a glvalue (“generalized” lvalue) is an expression whose evaluation determines the identity of an object or function.
gl-value("generalized:일반화된" l-value)는 객체나 함수의 identity를 결정하는 표현식이다.
x-value와 l-value가 여기에 속한다.
identity를 가진다는 것은 표현식(expression)을 진행한 이후에도 메모리를 점유하고 있다는 뜻이다.(object가 남아있다는 것)
데이터를 저장할 수 있는 메모리의 위치 정보, 즉 주소를 가지고 있다.
따라서 gl-value에 속하는 l-value나 r-value는 incomplete한 타입일 수 있고, polymorphic(다형성)일 수도 있다.
2. r-value (m)
an rvalue (so-called, historically, because rvalues could appear on the right-hand side of an assignment expression) is a prvalue or an xvalue.
r-value (이전에 assignment expression(operator =)의 오른편에 쓸 수 있었기 때문에 r-value라고 불렀었다.)는 pr-value 혹은 x-value를 말한다.
Im(identity 없음, move 가능)이 pr-value인 이유는 r-value가 따로 있기 때문이다.
x-value와 pr-value가 여기에 속한다.
C++ 11이후부터는 move가 가능한 값들, 즉 메모리 주소에서 이동이 가능한 값들을 r-value라고 한다.
참고한 사이트
https://en.cppreference.com/w/cpp/language/value_category
https://blog.seulgi.kim/2017/06/cpp11-value-category.html
https://dydtjr1128.github.io/cpp/2019/06/10/Cpp-values.html