← 문제로

56. const 정확성: const 멤버 함수·mutable·const 오버로딩·const_cast

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

해설 — const 정확성: const 멤버 함수·mutable·const 오버로딩·const_cast

난이도: 중

요약

this 의 const 여부가 오버로드 해석에 참여한다. const 객체/참조로 부르면 const 멤버가, 비-const 로 부르면 비-const 멤버가 선택된다. mutable 멤버는 논리적 const를 표현하기 위해 const 멤버 함수 안에서도 수정 가능하다. const_cast 는 "원래 const 로 정의된 객체"를 수정할 때만 UB 이고, const 참조가 실제로는 비-const 객체를 가리키면 벗겨내 수정해도 합법이다.

예측 출력

10
10
2
non-const
const
changed
99

단계별 내부 동작

  1. w.get() → 10. get() 은 const 멤버지만 ++accessCount 가 가능하다. accessCount 가 mutable 이라 const 멤버 함수의 "이 객체를 논리적으로 바꾸지 않는다"는 계약을 어기지 않고 물리적 비트만 갱신하기 때문. accessCount 0→1.
  2. cw.get() → 10. cw 는 const Widget& 지만 get() 이 const 멤버라 호출 가능하고, 여기서도 mutable 이라 accessCount 1→2.
  3. w.accessCount → 2. const 메서드가 두 번 다 mutable 을 증가시켰음을 확인.
  4. w.name() → 비-const 객체 w 로 호출 → 비-const 오버로드 선택 → "non-const" 출력, 반환된 std::string& 에 "changed" 대입.
  5. cw.name() → const 참조 cw 로 호출 → const 오버로드 선택 → "const" 출력, const std::string& 반환.
  6. n → "changed". 두 오버로드는 같은 name_ 를 반환하므로, 4에서 바꾼 값이 그대로 보인다.
  7. const_cast<Widget&>(cw).value = 99 → cw 가 실제로 가리키는 객체는 비-const 인 w 이므로 const 를 벗겨 수정해도 정의된 동작. w.value → 99.

const 오버로딩 규칙

  • 멤버 함수의 const 한정자는 시그니처의 일부다. T& f() 와 const T& f() const 는 서로 다른 오버로드이고, 호출 객체의 const 여부가 선택을 결정한다.
  • 관용구: 컨테이너의 operator[], at(), begin() 등이 이 쌍으로 제공되어, const 객체에서는 읽기 전용 참조를, 비-const 객체에서는 쓰기 가능한 참조를 돌려준다. 코드 중복을 줄이려면 비-const 버전에서 const_cast 로 const 버전을 호출하는 관용구(또는 C++23 deducing this)를 쓴다.

흔한 오해·함정

  • mutable 남용: mutable 은 캐시·지연 초기화·통계 카운터·mutex 처럼 "관찰 가능한 값은 그대로인데 내부 부수 상태만 바뀌는" 경우에만. 실제 논리 상태를 mutable 로 바꾸면 const 의 의미가 무너진다.
  • const_cast 로 진짜 const 수정 = UB: const Widget cobj; 처럼 원래 const 로 정의된 객체를 const_cast 로 벗겨 쓰면 정의되지 않은 동작이다. 컴파일러가 그 객체를 읽기 전용 메모리에 두거나 값을 상수 폴딩할 수 있다. 본 코드가 안전한 건 대상 객체 w 가 비-const 이기 때문일 뿐이다.
  • 비트wise vs 논리적 const: 컴파일러가 강제하는 것은 비트wise const(멤버 비트를 안 바꿈)다. mutable 과 포인터 멤버가 가리키는 대상 수정은 이 검사를 빠져나간다. 설계자가 지켜야 할 것은 논리적 const(관찰 가능한 상태 불변)다.

면접 포인트

  • "const 객체에서 왜 이 오버로드가 불리나?" → this 의 const 여부가 오버로드 해석에 참여하기 때문. const 정확성은 API 가 읽기/쓰기 의도를 타입으로 표현하게 해준다.
  • mutable 의 정당한 용례(캐시·통계·동기화 프리미티브)와 const_cast 의 UB 경계(원래 const 객체 수정)를 구분해 설명할 수 있어야 한다.