L-value vs R-value
정의
- L-value: 이름/식별성이 있고 주소를 가질 수 있는 값(변수, 참조 반환값 등)
- R-value: 주로 임시값/리터럴처럼 곧 사라지는 값
int a = 3;
참조 바인딩 규칙 (중요)
| 파라미터 타입 | L-value 인수 | R-value 인수 |
|---|
T& | 가능 | 불가 |
const T& | 가능 | 가능 |
T&& | 불가 | 가능 |
void F1(Knight& k);
void F2(const Knight& k);
void F3(Knight&& k);
F1(Knight())는 컴파일 에러
F2(Knight()), F3(Knight())는 가능
오른값 참조 &&
의미
T&&는 "이 값은 곧 버릴 가능성이 높은 임시값"을 받는 통로입니다.
- 이 통로를 통해 자원 복사 대신 소유권 이전(move)을 구현할 수 있습니다.
std::move
std::move(x)는 x를 rvalue로 캐스팅합니다.
- 이름과 달리 move 자체가 이동을 실행하는 함수는 아닙니다.
- 실제 이동은 이후 호출되는 move constructor / move assignment에서 일어납니다.
std::string a = "hello";
std::string b = std::move(a);
이동 생성자 vs 이동 대입 연산자
| 구분 | 시그니처 | 호출 시점 |
|---|
| 이동 생성자 | Knight(Knight&& other) | 새 객체를 만들 때 |
| 이동 대입 연산자 | Knight& operator=(Knight&& other) | 기존 객체에 대입할 때 |
실무 팁:
- move 연산에
noexcept를 붙이면 컨테이너(vector)가 재할당 시 move를 더 적극적으로 선택합니다.
이동 vs 복사
복사
- 자원을 새로 복제합니다(독립된 두 객체).
- 원본/대상이 모두 온전합니다.
이동
- 자원 핸들을 넘기고 원본은 비워 둡니다.
- 큰 문자열/버퍼/동적 메모리 객체에서 성능 이점이 큽니다.
vector 재할당 시 요소를 move하면 복사 비용을 크게 줄일 수 있습니다.
이동 후 객체 상태
- moved-from 객체는 파괴 가능하고 대입 가능한 유효 상태여야 합니다.
- 하지만 값 내용은 보장되지 않습니다(보통 "비어 있음"처럼 보일 수 있으나 규약은 아님).
std::string s = "abc";
std::string t = std::move(s);
s = "reused";
실전 사용 규칙 + 체크 질문
실전 규칙
- 소유권 이전 시점에만
std::move를 사용합니다.
- move 후 원본 값을 곧바로 읽는 습관은 피합니다.
const 객체에 std::move를 해도 진짜 move가 안 되는 경우가 많습니다(복사로 귀결).
- 지역 변수 반환에서 무조건
std::move를 붙이지 않습니다(NRVO/복사 생략 기회 감소 가능).
std::unique_ptr<Knight> MakeKnight() {
auto k = std::make_unique<Knight>();
return k;
}
체크 질문 (스스로 답해보기)
std::move가 "이동 실행"이 아니라 "캐스팅"이라는 뜻을 설명할 수 있는가?
- 왜 moved-from 객체는 "유효하지만 값은 미정"이라고 하는가?
const T에 std::move를 걸면 왜 기대한 move가 안 나올 수 있는가?