어떤 값이 불변이어야 하는 제작자의 의도를 컴파일러 및 다른 프로그래머와 나눌 수 있는 수단
const
키워드가 *
보다 왼쪽에 있으면 포인터가 가리키는 대상이 상수임
*
보다 오른쪽에 있으면 포인터 자체가 상수임
char greeting[] = "Hello";
char* p = greeting; // 비상수 포인터, 비상수 데이터 (값, 주소 변경 가능)
const char* p = greeting; // 비상수 포인터, 상수 데이터 (주소 변경 가능)
char* const p = greeting; // 상수 포인터, 비상수 데이터 (값 변경 가능)
const char* const p = greeting; // 상수 포인터, 상수 데이터
포인터가 가리키는 대상을 상수로 만들 때 두 가지 방법이 있지만 의미적인 차이는 전혀 없음
void f1(const Widget *pw); // (1)
void f2(Widget const *pw); // (2)
멤버 함수에 붙는 const
는 해당 멤버 함수가 상수 객체에 대해 호출될 함수임을 알려줌
상수 멤버 함수를 사용하는 이유
물리적 상수성
어떤 멤버 함수가 그 객체의 어떤 데이터 멤버도 건드리지 않아야 함 (정적 멤버 제외)
즉, 그 객체를 구상하는 비트들 중 어떤 것도 바꾸면 안 됨
논리적 상수성
상수 멤버 함수라고 해서 객체의 한 비트도 수정할 수 없는 것이 아니라 일부 몇 비트 정도는 바꿀 수 있되, 그것을 사용자 측에서 알아채지 못하게만 하면 상수 멤버 자격이 있음
-> 컴파일러가 물리적 상수성을 요구하는 경우 mutable
키워드를 사용하여 논리적 상수성만 지킬 수 있음
비상수 멤버 함수를 캐스팅해서 상수 멤버 함수를 호출하면 됨
반대의 경우는 상수성을 제거하고 비상수 멤버를 호출하게 되는 것이므로 안정성에 문제가 있음
class TextBlock
{
public:
const char& operator[](std::size_t position) const
{
return text[position];
}
const char& operator[](std::size_t position)
{
return const_cast<char&>(
static_cast<const TextBlock&>
(*this)[position]
);
}
};
단순히 const
를 쓰지 않으면 객체의 상태 변경이 자유롭게 이루어지기 때문에 객체의 상태가 예기치 않게 변할 수 있음
const
를 사용하는 목적은 객체의 외부에서 값이 바뀌지 않도록 보호하기 위해서임
mutable
은 const
로 선언된 객체 내부에서 특정 멤버만 수정할 수 있도록 허용하기 때문에 외부에서는 불변성을 유지하면서도 내부적으로 필요한 수정을 허용할 수 있음