C++에는 다양한 저장 클래스(storage class)가 존재하며, 그중 하나가 바로 mutable입니다. mutable로 선언된 데이터 멤버는 객체가 const 타입이라 하더라도 언제든지 값을 변경할 수 있습니다. 즉, 여러 멤버 중 일부만 수정 가능하게 두고 나머지는 상수처럼 보호하고 싶을 때 mutable을 활용하면 매우 유용합니다.
그렇다면 실제 코드에서 어떻게 동작하는지 예제를 통해 살펴보겠습니다.
예제 코드
#include <iostream>
using namespace std;
class MyClass{
int x;
mutable int y;
public:
MyClass(int x=0, int y=0){
this->x = x;
this->y = y;
}
void setx(int x=0){
this->x = x;
}
void sety(int y=0) const { // 멤버 함수는 const이지만, mutable 멤버는 값 변경 가능
this->y = y;
}
void display() const{
cout<<endl<<"(x: "<<x<<" y: "<<y << ")"<<endl;
}
};
int main(){
const MyClass s(15,25); // const 객체 생성
cout<<endl <<"Before Change: ";
s.display();
s.setx(150);
s.sety(250);
cout<<endl<<"After Change: ";
s.display();
}실행 결과 (컴파일 오류)
[Error] passing 'const MyClass' as 'this' argument of 'void MyClass::setx(int)' discards qualifiers [-fpermissive]
오류가 발생한 이유
위 코드를 그대로 실행하면 컴파일 오류가 발생합니다. s는 const MyClass 타입의 객체이기 때문에, x 멤버를 수정하려는 setx() 함수를 호출할 수 없습니다. const 객체는 비상수(non-const) 멤버 함수를 호출할 수 없기 때문입니다.
setx 호출 제거 후 실행 결과
반면 s.setx(150); 라인을 제거하고 실행하면 어떻게 될까요?
Before Change: (x: 15 y: 25) After Change: (x: 15 y: 250)
결과 분석
흥미로운 점은 y의 값이 25에서 250으로 성공적으로 변경되었다는 것입니다. sety() 함수는 const 멤버 함수임에도 불구하고 y를 수정할 수 있었는데, 이는 y가 mutable로 선언되어 있기 때문입니다.
정리하면 다음과 같습니다.
- const 객체: 일반 멤버 변수(
x)는 어떤 방법으로도 수정할 수 없습니다. - mutable 멤버(
y): const 객체 안에서도, const 멤버 함수 내부에서도 자유롭게 값을 변경할 수 있습니다.
이러한 특성 덕분에 mutable은 캐시(cache), 뮤텍스(mutex), 디버깅용 카운터 등 객체의 논리적 상태에는 영향을 주지 않으면서 내부적으로 갱신이 필요한 멤버를 구현할 때 널리 사용됩니다.