C++에서 lvalue (왼쪽 값)는 메모리의 객체를 참조하는 표현식으로 이름이 있으며 참조할 수 있는 것입니다(예: 변수). rvalue (오른쪽 값)는 이름이 없는 임시 값으로 전통적인 의미의 객체가 아닙니다(예: a + b의 결과, 타입 5의 리터럴).
lvalue는 주소를 가져올 수 있지만, rvalue는 그렇지 않습니다 (rvalue 참조가 등장하기 전까지). 이들을 함수에 전달하기 위해서는 다음과 같은 방법이 있습니다:
void foo(const std::string& s); — lvalue와 rvalue를 모두 받습니다.void bar(std::string& s); — lvalue만 받습니다.void baz(std::string&& s); — rvalue만 받습니다.예제:
void takeValue(std::string& s) { } // lvalue void takeRValue(std::string&& s) { } // rvalue std::string s = "hello"; takeValue(s); // OK, lvalue takeRValue(std::string("hi")); // OK, rvalue
rvalue 참조는 주로 자원을 복사하는 것이 아니라 이동하기 위해 임시 객체를 효율적으로 전달하는 데 필요합니다.
표현식
std::move(obj)가 어떤 유형의 참조(lvalue 또는 rvalue)를 받을 것인가요? std::move를 적용한 후 객체는 어떤 카테고리에 속하게 되나요?
답변:
std::move(obj)는 항상 rvalue 참조 (T&&)를 반환하지만, 객체 자체는 여전히 lvalue로 남아 있으며 단지 명시적 변환이 적용됩니다. 이후에는 객체를 극도로 조심스럽게 처리해야 합니다 (정의되지 않았지만 유효한 상태일 수 있습니다).
예제:
std::string s = "data"; std::string d = std::move(s); // d는 s의 데이터를 얻고, s는 이제 비어있음
이야기
대형 프로젝트에서 한 개발자는 임시 객체를 lvalue 참조 T&를 통해 전달했습니다 (대신 T&& 또는 const T&). 이는 컴파일 오류와 비효율적인 복사를 초래했습니다 — move 의미론이 사용되지 않아 성능이 40% 감소했습니다.
이야기
프론트엔드 엔진에서 std::move가 잘못 적용되어 다시 사용한 변수들이 있었습니다. 이로 인해 변수들이 "파괴된" 상태가 되었고, 이로 인해 예외가 발생하고 렌더 스레드가 충돌했습니다.
이야기
직렬화 라이브러리에서 std::vector<T> 타입의 컨테이너를 함수에 lvalue로 전달했지만 이동(movable)을 기대하였습니다. 이동 대신 많은 요소의 비싼 복사가 진행되어 큰 배열의 직렬화 시간이 급격히 악화되었습니다.