상수가 존재하는 이유
상수는 한 번 설정한 뒤에는 절대 바꾸지 않겠다고 약속하는 값입니다. 무언가를 const로 표시하면 두 가지 일을 동시에 합니다. 코드를 읽는 누구에게나 여러분의 의도를 문서화하고, 컴파일러가 그 의도를 강제하게 합니다. 값을 변경하려는 줄은 조용한 런타임 버그가 아니라 컴파일 오류가 됩니다.
auto 키워드가 컴파일러에게 변수의 타입을 추론하게 하는 반면, const는 그 변수로 할 수 있는 일을 제한합니다. 둘은 자유롭게 결합됩니다. const auto limit = 100;은 읽기 전용 int입니다.
const 값 선언하기
const를 타입 앞에 둡니다. const 변수는 같은 줄에서 반드시 초기화해야 합니다. 나중에 대입할 수 있는 순간이 존재하지 않기 때문입니다.
대입의 주석을 해제하면 프로그램은 컴파일되지 않습니다. 컴파일러는 "assignment of read-only variable"이라고 알려줍니다. 바로 그것이 핵심입니다. 실수는 프로그램이 실행되기도 전에 잡힙니다.
C에서 가져온 흔한 초보자 습관이 #define MAX_USERS 100입니다. 피하세요. 매크로는 타입이 없고 스코프를 존중하지 않는 맹목적인 텍스트 치환이라 디버거에서 살펴볼 수 없고 혼란스러운 오류 메시지를 만들어냅니다. const(또는 constexpr) 변수는 다른 모든 것과 마찬가지로 타입 검사를 받고 스코프를 가집니다.
const vs constexpr
두 키워드 모두 바꿀 수 없는 값을 주지만, 답하는 질문이 다릅니다. const는 "이것은 설정된 뒤로 절대 바뀌지 않는다"고 말합니다. constexpr은 더 강하게 "이것은 컴파일 타임에 계산될 수 있다"고 말하며, constexpr인 것은 무엇이든 자동으로 const이기도 합니다.
경험칙: 값이 고정된 리터럴이거나 컴파일러가 할 수 있는 계산(배열 크기, 버퍼 길이, switch 레이블, 템플릿 인자)일 때는 언제든 constexpr을 선택하세요. 값이 런타임에 결정되지만 그 후에는 바뀌면 안 될 때(예: 함수 인자의 const 사본)는 일반 const를 사용하세요.
C++20부터는 반드시 컴파일 타임에 실행되어야 하는 함수에 사용하는 consteval도 있습니다.
consteval int square(int x) { return x * x; }
constexpr int area = square(8); // 컴파일 중에 계산됨
constexpr 함수는 컴파일 타임에 실행될 수 있고, consteval 함수는 항상 그래야 하며, 그렇지 않으면 오류입니다.
포인터와 const: 오른쪽에서 왼쪽으로 읽기
여기가 const가 사람들을 헷갈리게 하는 지점입니다. 키워드가 *의 어느 쪽에든 놓일 수 있고 두 의미가 정반대이기 때문입니다. 요령은 선언을 오른쪽에서 왼쪽으로 읽는 것입니다.
int* const p2를 오른쪽에서 왼쪽으로 읽으세요: "p2는 int를 가리키는 const 포인터". const int* p1은 "p1은 const int를 가리키는 포인터"로 읽습니다. 이것을 틀리면, 변경 가능하다고 생각했던 것을 변경할 수 없다는 오류에 당황하며 실제로 시간을 낭비하게 됩니다.
실용적인 함정: 절대 const의 주소를 가져와 캐스트로 const를 떼어내 그 밑에 있는 객체를 변경하지 마세요. 원래 객체가 정말로 const였다면 그렇게 하는 것은 정의되지 않은 동작이고, 컴파일러는 값이 절대 바뀌지 않는다고 가정할 수 있습니다. 여러분의 "쓰기"는 그냥 무시될 수 있습니다.
함수 매개변수로서의 const 참조
const의 가장 흔한 일상적 용도는 큰 객체를 복사하지 않고 참조로 전달하는 것입니다. const& 매개변수는 복사를 피하는 동시에 함수가 호출자의 인자를 수정하지 않겠다고 약속합니다.
const& 전달은 몇 바이트보다 큰 매개변수(문자열, 벡터, 직접 만든 클래스)에 대한 기본 선택입니다. 이것은 함수가 "Grace" 같은 임시 객체를 받을 수 있게도 해준다는 점에 유의하세요. 일반적인 비-const 참조는 임시 객체에 바인딩될 수 없으므로, 여기서 const를 빼면 그 두 번째 호출이 거부됩니다.
const 멤버 함수
클래스를 작성할 때는 객체를 수정하지 않는 메서드에 끝에 const를 붙이세요. 이것이 그 메서드를 const 인스턴스와 const& 매개변수에서 호출할 수 있게 만듭니다. 이것이 없으면 const 핸들을 통해 자신의 객체를 읽을 수조차 없습니다.
읽기 전용 메서드를 const로 표시하는 규율을 const correctness라고 합니다. 일찍 제대로 해두세요. const였어야 할 메서드를 추가하기는 쉽지만, 나중에 큰 코드베이스 전반에 const를 소급 적용하는 것은 고통스럽습니다. const 참조를 통한 모든 호출자가 그것에 의존하기 때문입니다.
다음: 연산자
이제 값을 const로 잠글 수 있게 되었으니, 다음 단계는 그 값으로 무언가를 하는 것입니다. 연산자 페이지에서는 산술, 비교, 논리, 대입 연산자를 다룹니다. 정수 나눗셈에 얽힌 함정, 연산자 우선순위, 그리고 사용이 허용되지 않는 대입 연산자와 const가 어떻게 상호작용하는지도 포함합니다.
자주 묻는 질문
C++에서 const와 constexpr의 차이는 무엇인가요?
const는 초기화 후 값을 바꿀 수 없다는 뜻이지만, 그 값은 런타임에 계산될 수 있습니다. constexpr은 더 강력합니다. 값이 컴파일 타임에 계산될 수 있음을 보장하므로, 컴파일 타임 상수가 필요한 곳(배열 크기, 템플릿 인자, switch 레이블)에서 사용할 수 있습니다. 모든 constexpr 객체는 const이기도 하지만, 모든 const 객체가 constexpr인 것은 아닙니다.
C++에서 상수를 어떻게 선언하나요?
타입 앞에 const를 붙이고 값을 줍니다: const int maxUsers = 100;. const 변수는 선언할 때 반드시 초기화해야 합니다. 나중에는 절대 값을 대입할 수 없기 때문입니다. 컴파일 타임 상수에는 constexpr int maxUsers = 100;을 선호하세요. 타입이 없고 스코프를 무시하는 옛 C 스타일 #define 매크로는 피하세요.
C++에서 const 포인터는 무엇을 의미하나요?
const가 어디에 있느냐에 따라 다릅니다. const int* p는 const를 가리키는 포인터로, p가 가리키는 대상을 바꿀 수는 있지만 *p는 변경할 수 없습니다. int* const p는 const 포인터로, *p는 변경할 수 있지만 p가 가리키는 대상은 바꿀 수 없습니다. 선언은 오른쪽에서 왼쪽으로 읽으세요: int* const는 "int를 가리키는 const 포인터"입니다.