[C++] value(lvalue, rvalue, xvalue)와 레퍼런스(&)
서론
https://k99812.tistory.com/151
[C++] 좌측값과 우측값 그리고 Move, Forward 함수
좌측값과 우측값 L-value이름이 있고 메모리 주소가 있는 값 R-value일시적인 값, 임시 객체 또는 계산 결과 등int x = 10; // x는 lvalue, 10은 rvalueint y = x + 5; // y는 lvalue, (x + 5)는 rvalue C++ remove_reference_t 함
k99812.tistory.com
지난 글에서 좌측값과 우측값에 대해 글을 작성했었다. 하지만 여기서 쓴 예전 좌측값 우측값 개념으로는
move함수와 move semantics를 이해하는데 어려워 다시한번 글을 쓰게됐다.
지난글에는 move, forward, universal reference, reference collapsing에 대해 정리했다
새로운 value 분류
C++ 11에서 등장한 우측값 참조는 기존에 있던 lvalue, rvalue의 개념만으로는 명확히 설명하기 어려워
xvalue, glvalue, prvalue라는 새로운 값 분류 개념이 추가되다
이러한 개념이 도입된 이유는 좌측값 변수를 우측값 변수처럼 다루어 우측값 레퍼런스를
사용해 이동 연산자, 생성자를 사용하여 변수의 데이터(소유권)를 이동시키기위해서다
그러나 기존 개념으론 좌측값을 move 함수를 통해 우측값 참조로 변환된 변수(move함수 리턴 값)가
실제로 좌측값인지 우측값인지를 정확히 구분하기 어렵다 이 문제를 해결하고자 xvalue 개념이 등장했다
새롭게 추가된 xvalue는 기존 개념인 좌측값과 우측값의 특징을 둘다 가질 수 있는 value이다
그래서 lvalue와 xvalue가 합쳐진 glvalue 그리고 prvalue와 xvalue가 합쳐진 rvalue가 추가됐다
- glvalue = lvalue + xvalue
- glvalue의 g는 generalized (일반화된)이다
- Identity(이름이 있다)
해당 이름을 통해 데이터의 주소로 접근할 수 있다 - 즉 이름과 주소가 존재한다할 수 있다.
- rvalue = prvalue + xvalue
- 이동할 수 있다
임시 객체, 함수 반한값 또는 임시 값 등의 메모리에 있는 데이터(소유권)를 다른 변수에 넘겨줄 수 있다. - 소멸할 수 있다
값 or 함수 반한값은 바로 소멸하고 xvalue의 경우는 이동 생성자 or 연산자(구현된 경우)를 통해 데이터를 넘겨주게 된다 - 즉 이동(소멸)가능한 임시 객체라고 할 수 있다.
- 이동할 수 있다
- lvalue
- Identity를 가지고 있지만 이동대상이 아니다
Identity를 가지고 있어 이름을 통해 주소로 접근 가능하다
- Identity를 가지고 있지만 이동대상이 아니다
- prvalue
- prvalue는 pure rvalue(순수 rvalue)이다
- 주소가 없다
임시 객체로서 주소는 존재하지만 이름이 존재하지 않아 사용자는 임시객체(값)의 주소로 접근할 수 없다 - 즉 이름과 주소가 없다(주소에 접근할 수 없다)
- move함수를 사용하여 xvalue로 바꿀수 있지만 이미 move대상(이동 생성자, 연산자)이므로 의미가 없다
- xvalue
- 소멸(이동)하기 직전의 값 eXpiring value이다
- rvalue기반 xvlaue
prvalue의 특징을 가져 주소가 없고 이름이 없다 - glvalue기반 xvalue
lvalue의 특징인 Identity가 있어 이름이 있고 주소가 있다 - 사용자가 &를 사용하여 xvalue의 주소에 접근할 수 없다
&로 주소를 가져오는 것은 lvalue만 가능하다
그 이유는 xvalue가 위의 경우처럼 주소가 존재하거나 존재 하지않을 수도 있기 때문이다 - move함수를 사용하여 xvalue로 변환하는 것은 대부분 lvalue이다
xvalue의 개념이 lvalue를 xvalue로 변환하여 좌측값 레퍼런스(복사)가 아닌
우측값 레퍼런스(이동)를 사용하기 위해 나왔기 때문이다 - move함수를 통해 rvalue를 변환하는 것은 문제가 없지만
실질적으로 rvalue는 이미 우측값 레퍼런스를 사용할 수 있기 때문에 사용하지 않는다
int num = 5;
int&& ref = std::move(num); // ✅ OK xvalue → rvalue reference
int* ptr1 = &ref; // ✅ OK
int* ptr2 = &std::move(num); // ❌ C2102: '&' requires l-value
&ref가 가능한 이유는 어떤 참조든(&, &&) 변수자체는 항상 lvalue(이름과, 주소가 있음)이다
std::move 함수
move는 lvalue를 rvalue처럼 이동 생성자, 연산자를 사용하기 위해 lvalue를 xvalue로 변환한다
xvalue로의 변환은 좌측값 변수를 우측값 참조 타입으로 캐스팅 한다 결과적으로 move함수를 통해
lavalue를 xvalue로 변환하여 이동 시멘틱을 사용하게 된다
string a = "hello";
string b = move(a); // 이동 생성자 호출
cout << "a : " << a << "\n"; // 출력 a :
cout << "b : " << b << "\n"; // 출력 b : hello
- a를 move를 통해 우측값 레퍼런스 타입으로 캐스팅(xvalue 변환)
- 이동생성자가 호출되며 이동생성자에서 a의 값을 b에 저장 후 a의 값은 초기화
& 레퍼런스
참조
int num = 5;
int& lref = num;
int&& rref = move(num);
cout << &num << "\n"; //num의 주소
cout << &lref << "\n"; //num의 주소
cout << &rref << "\n"; //num의 주소
좌측값 참조(&), 우측값 참조(&&)는 똑같이 주소를 저장한다
차이점은 &는 좌측값의 주소를 참조, &&는 우측값(prvalue + xvalue)의 주소를 참조한다
활용
생성자
class MyClass
{
public:
vector<int> data;
MyClass()
{
cout << "기본 생성자\n";
}
// 복사 생성자
MyClass(const MyClass& other)
{
data = other.data; // 복사
cout << "복사 생성자\n";
}
// 이동 생성자
MyClass(MyClass&& other) noexcept
{
data = move(other.data); // 이동
cout << "이동 생성자\n";
}
};
MyClass a; // 기본 생성자 호출
MyClass b = a; // 복사 생성자 호출 (lvalue)
MyClass c = move(a); // 이동 생성자 호출 (xvalue)
기본 생성자, 복사 생성자, 이동 생성자 예시 코드
class MyClass
{
public:
vector<int> data;
MyClass()
{
cout << "기본 생성자\n";
}
// 복사 생성자
MyClass(const MyClass& other)
{
data = other.data; // 복사
cout << "복사 생성자\n";
}
};
MyClass a; // 기본 생성자 호출
MyClass b = a; // 복사 생성자 호출 (lvalue)
MyClass c = move(a); // 복사 생성자 호출 (xvalue)
만약 이동 생성자가 정의 되어 있지 않으면 복사 생성자가 호출됨
Foward
class MyClass
{
public:
vector<int> data;
MyClass()
{
cout << "기본 생성자\n";
}
// 복사 생성자
MyClass(const MyClass& other)
{
data = other.data; // 복사
cout << "복사 생성자\n";
}
// 이동 생성자
MyClass(MyClass&& other) noexcept
{
data = move(other.data); // 이동
cout << "이동 생성자\n";
}
};
template<typename T>
T makeClass(T&& inClass)
{
return T(forward<T>(inClass));
}
MyClass a; // 기본 생성자 호출
MyClass b = makeClass(a); // 복사 생성자 호출 (lvalue)
MyClass c = makeClass(move(a)); // 이동 생성자 호출 (xvalue)
universal reference와 forward를 통한 완벽전달을 사용한 예시 코드