C++에서 모든 표현식(expression)은 타입과 값 범주(Value category) 두 가지 속성을 가진다.

"위치를 가진 값" - 메모리 주소를 가질 수 있는 표현식
#include <iostream>
using namespace std;
int main()
{
int x = 10;
int* ptr = &x; // x는 glvalue (주소를 가질 수 있음)
int& ref = x;
int* ptr2 = &ref; // ref도 glvalue
int arr[3] = { 1, 2, 3 };
int* ptr3 = &arr[0]; // arr[0]도 glvalue
return 0;
}
"임시 값" - 곧 사라질 값
#include <iostream>
using namespace std;
int main()
{
int x = 10;
// int* ptr = &10; // 에러: 10은 rvalue (주소 없음)
// int* ptr2 = &(x + 5); // 에러: x+5는 rvalue
int y = x + 5; // OK: rvalue를 대입 연산자 오른쪽에 사용
return 0;
}
"이름이 있고" 지속되는 객체
#include <iostream>
using namespace std;
int globalVar = 100;
int& getRef()
{
return globalVar;
}
int main()
{
int x = 10; // 1. 변수
// x는 lvalue
int arr[3] = { 1, 2, 3 };
arr[0] = 5; // 2. 배열 요소
// arr[0]는 lvalue
const char* str = "Hello";
// 3. 문자열 리터럴
// "Hello"는 lvalue (특수 케이스)
getRef() = 200; // 4. 참조를 반환하는 함수
// getRef()는 lvalue
int y = 5;
++y = 10; // 5. 전위 증가 연산자
// ++y는 lvalue (y 자체를 반환)
int* ptr = &x;
*ptr = 20; // 6. 역참조 연산자
// *ptr은 lvalue
cout << "x = " << x << endl;
cout << "y = " << y << endl;
cout << "globalVar = " << globalVar << endl;
return 0;
}
문자열 리터럴은 lvalue
#include <iostream> using namespace std; int main() { // 문자열 리터럴은 lvalue! const char* ptr = "Hello"; // OK // 주소를 가질 수 있음! cout << "문자열 주소: " << (void*)"Hello" << endl; cout << "같은 문자열 주소: " << (void*)"Hello" << endl; // 같은 주소! // 배열처럼 접근 가능 cout << "첫 글자: " << "Hello"[0] << endl; // 'H' // 포인터 연산 가능 cout << "두 번째 글자: " << *("Hello" + 1) << endl; // 'e' return 0; }#include <iostream> using namespace std; int main() { // 문자열 리터럴은 lvalue! const char* ptr = "Hello"; // OK // 주소를 가질 수 있음! cout << "주소: " << (void*)"Hello" << endl; // OK // 참조로 받을 수 있음! const char(&ref)[6] = "Hello"; // OK (배열 참조) cout << ref << endl; return 0; }정적 메모리 영역에 저장됨
"순수한 임시 값" - 위치가 없는 값
#include <iostream>
using namespace std;
class Number
{
public:
int value;
Number(int v) : value(v)
{
cout << "생성자: " << value << " (this: " << this << ")" << endl;
}
~Number()
{
cout << "소멸자: " << value << " (this: " << this << ")" << endl;
}
};
Number createNumber()
{
return Number(100); // Number(100)는 prvalue
}
int getValue()
{
return 42; // 42는 prvalue
}
int main()
{
// 1. 리터럴
int x = 10; // 10은 prvalue
// 2. 산술 연산 결과
int y = 5 + 3; // 5+3은 prvalue
// 3. 후위 증가 연산자
int z = 7;
int w = z++; // z++는 prvalue (임시 값 반환)
// 4. 값을 반환하는 함수
int result = getValue(); // getValue()는 prvalue
// 5. 임시 객체
Number n = createNumber(); // createNumber()는 prvalue
// 6. 임시적 생성자 호출
Number temp = Number(50); // Number(50)는 prvalue
// 7. 캐스팅
double d = static_cast<double>(10); // static_cast<double>(10)는 prvalue
// 8. this 포인터
// this는 prvalue (포인터 값 자체)
return 0;
}
"만료되는 값" - 곧 사라질 객체지만 위치는 있음
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <utility>
using namespace std;
class String
{
private:
char* data;
public:
String(const char* str)
{
data = new char[strlen(str) + 1];
strcpy(data, str);
cout << "생성자: " << data << " (주소: " << (void*)data << ")" << endl;
}
~String()
{
cout << "소멸자: " << (data ? data : "nullptr") << endl;
delete[] data;
}
String(String&& other) noexcept
{
cout << "이동 생성자 호출!" << endl;
data = other.data;
other.data = nullptr;
}
};
String&& getRvalueRef()
{
static String s("Static");
return std::move(s); // xvalue 반환
}
int main()
{
// 1. std::move의 결과
String s1("Hello");
String s2 = std::move(s1); // std::move(s1)는 xvalue
// 2. rvalue 참조로 캐스팅
String s3("World");
String s4 = static_cast<String&&>(s3); // xvalue
// 3. rvalue 참조를 반환하는 함수
String s5 = getRvalueRef(); // xvalue
// 4. 배열의 이동
String arr[2] = { String("A"), String("B") };
String s6 = std::move(arr[0]); // std::move(arr[0])는 xvalue
return 0;
}
