← 문제로

47. 멤버 함수의 참조 한정자(ref-qualifier)와 값 범주 기반 오버로드

난이도 중
내 리뷰 · C++
해설 · C++

해설 — 멤버 함수의 참조 한정자(ref-qualifier)와 값 범주 기반 오버로드

난이도: 중상

요약

멤버 함수 뒤의 &/&&/const & 는 호출 대상 객체(*this)의 값 범주로 오버로드를 고르게 한다. lvalue 객체에서는 const & 버전이, rvalue(임시·std::move 된) 객체에서는 && 버전이 선택된다. 그래서 임시 객체에서는 안전하게 내부 자원을 이동해 복사를 피할 수 있다.

출력

get() const& -> copy
get() && -> move
get() && -> move
lvalue / temp / lvalue

핵심 개념

  • ref-qualifier 는 *this 의 값 범주를 대상으로 한 오버로드: get() const & 는 "이 객체가 lvalue 일 때", get() && 는 "이 객체가 rvalue 일 때" 호출된다. 일반 함수의 인자 오버로드 (f(const T&) vs f(T&&))와 같은 원리를, 암시적 인자인 *this 에 적용한 것이다.
  • 값 범주 판정:
    • (1) lv.get(): lv 는 이름 있는 lvalue → const & 선택 → copy. a = "lvalue".
    • (2) makePayload().get(): 함수가 돌려준 prvalue(임시) → && 선택 → move. b = "temp".
    • (3) std::move(lv).get(): std::move 가 lv 를 xvalue(rvalue)로 캐스팅 → && 선택 → move. c = "lvalue"(이동으로 가져온 값). 이후 lv.data 는 moved-from 상태다.

단계별 내부 동작

  1. 오버로드 집합에 ref-qualifier 가 있으면, 컴파일러는 호출식의 객체 표현식이 lvalue 인지 rvalue 인지 판정한다(값 범주 분석).
  2. lvalue 면 &(또는 const &) 후보만, rvalue 면 &&(그리고 rvalue 가 const-lvalue 참조에도 바인딩 가능하므로 const & 도) 후보가 된다. 그중 가장 잘 맞는 것을 고른다 — 여기선 rvalue 에 && 가 const & 보다 정확히 일치해 우선한다.
  3. 규칙 일관성: 한 멤버 함수 이름에 ref-qualified 버전을 하나라도 두면, 같은 시그니처의 모든 오버로드가 ref-qualified 이어야 한다(ref-qualified 와 비-qualified 를 섞을 수 없다).
  4. && 오버로드 안에서 std::move(data) 로 멤버를 이동한다. 대상 객체가 곧 소멸할 rvalue 이기 때문에 내부 자원을 훔쳐도 안전하다는 것이 이 오버로드의 존재 이유다.

흔한 오해·함정

  • "std::move(x).f() 는 x 를 이동한다" 가 아니라, && 오버로드를 선택할 뿐이다. 실제 이동은 그 오버로드가 내부에서 std::move 를 할 때 일어난다(안 하면 이동 안 됨).
  • const && 는 거의 무의미: const rvalue 는 이동할 수 없으므로 최적화 이득이 없다.
  • lvalue-only 강제: T& operator=(...) & 처럼 & 만 두면 임시 객체에 대입하는 실수 (makePayload() = other;)를 컴파일 단계에서 막을 수 있다(표준 라이브러리도 일부 대입 연산자에 사용).
  • moved-from 객체는 유효하지만 미지정 상태 — 이후 재사용 전 재대입해야 한다.

면접 포인트

  • ref-qualifier 가 인자 오버로드(const T&/T&&)와 어떻게 대응되는가(*this 의 값 범주).
  • 빌더/게터에서 임시 객체의 자원을 이동해 복사를 없애는 실전 패턴, std::move(obj).method() 의 의미.
  • & 한정으로 rvalue 대상 호출(임시에 대입 등)을 컴파일 타임에 금지하는 활용.