cv 한정자
cv 한정자 (const / volatile)
선언되는 객체가 상수인지 휘발성인지를 지정하기 위해 const와 volatile 한정자를 써요. 이들은 어떤 타입 지정자에도 나타날 수 있고, 선언 문법의 decl-specifier-seq에도 포함돼요.
출처: cppreference
본문
선언되는 객체 또는 명명되는 타입의 상수성(constness)이나 휘발성(volatility)을 지정하기 위해 어떤 타입 지정자(선언 문법의 decl-specifier-seq 포함)에도 나타날 수 있어요.
const— 타입이 상수(constant)임을 정의해요.volatile— 타입이 휘발성(volatile)임을 정의해요.
설명 (Explanation)
함수 타입이나 참조 타입이 아닌 모든 (아마도 불완전한) 타입은 다음 네 가지 서로 다르지만 관련된 타입의 그룹에 속해요:
- cv-unqualified 버전
- const-한정 버전
- volatile-한정 버전
- const-volatile-한정 버전
같은 그룹의 이 네 타입은 같은 표현(representation)과 정렬 요구 사항을 가져요.
배열 타입은 요소 타입과 같은 cv 한정을 갖는 것으로 간주돼요.
const 및 volatile 객체
객체가 처음 생성될 때 사용된 cv 한정자(decl-specifier-seq의 일부, 선언의 선언자 일부, 또는 new-expression의 type-id 일부일 수 있음)가 객체의 상수성이나 휘발성을 다음과 같이 결정해요:
- const 객체는
- 타입이 const-한정된 객체이거나,
- const 객체의 비mutable 부분 객체예요.
이런 객체는 수정할 수 없어요: 직접 수정하려 하면 컴파일 타임 오류이고, 간접적으로(예: 비-const 타입의 참조나 포인터를 통해 const 객체를 수정) 수정하면 undefined behavior예요.
- volatile 객체는
- 타입이 volatile-한정된 객체이거나,
- volatile 객체의 부분 객체이거나,
- const-volatile 객체의 mutable 부분 객체예요.
volatile-한정 타입의 glvalue 표현식을 통한 모든 접근(읽기 또는 쓰기 연산, 멤버 함수 호출 등)은 최적화 목적에서 가시적인 부수 효과로 취급돼요 (즉 단일 실행 스레드 내에서 volatile 접근은 최적화로 제거되거나 그 volatile 접근보다 시퀀스 지정되거나 그 뒤에 시퀀스 지정되는 다른 가시적 부수 효과와 재정렬될 수 없음. 이는 volatile 객체를 시그널 처리기와의 통신에 적합하게 만들지만 다른 실행 스레드와의 통신에는 부적합하게 해요, std::memory_order 참고). 비-volatile 타입의 glvalue(예: 비-volatile 타입의 참조나 포인터)를 통해 volatile 객체에 접근하려는 시도는 undefined behavior예요.
- const volatile 객체는
- 타입이 const-volatile-한정된 객체이거나,
- const volatile 객체의 비mutable 부분 객체이거나,
- volatile 객체의 const 부분 객체이거나,
- const 객체의 비mutable volatile 부분 객체예요.
const 객체이면서 volatile 객체인 것처럼 동작해요.
각 cv 한정자(const와 volatile)는 어떤 cv 한정자 시퀀스에서도 최대 한 번 나타날 수 있어요. 예를 들어 const const와 volatile const volatile은 유효한 cv 한정자 시퀀스가 아니에요.
mutable 지정자
mutable— 포함하는 객체가const로 선언되어 있어도 그렇게 선언된 클래스 멤버의 수정을 허용해요 (즉 그 클래스 멤버는 mutable).
비정적 클래스 멤버 중 비참조 비-const 타입의 선언에 나타날 수 있어요:
class X
{
mutable const int* p; // OK
mutable int* const q; // ill-formed
mutable int& r; // ill-formed
};
mutable은 그 멤버가 클래스의 외부적으로 보이는 상태에 영향을 주지 않는다는 것을 지정하는 데 쓰여요 (뮤텍스, 메모 캐시, 지연 평가, 접근 계측에 자주 쓰임).
class ThreadsafeCounter
{
mutable std::mutex m; // The "M&M rule": mutable and mutex go together
int data = 0;
public:
int get() const
{
std::lock_guard<std::mutex> lk(m);
return data;
}
void inc()
{
std::lock_guard<std::mutex> lk(m);
++data;
}
};
get()은 const 멤버 함수라도 data를 읽으려면 뮤텍스를 잠가야 해요. 그 뮤텍스 m을 mutable로 선언하지 않으면 const 함수 안에서 m을 잠글 수 없어요.
변환 (Conversions)
cv 한정자에는 제한이 증가하는 순서로 부분 순서가 있어요. 타입은 다음보다 더 또는 덜 cv-한정되었다고 말할 수 있어요:
- unqualified
<const - unqualified
<volatile - unqualified
<const volatile const<const volatilevolatile<const volatile
cv-한정 타입에 대한 참조와 포인터는 더 cv-한정된 타입에 대한 참조와 포인터로 암시적으로 변환될 수 있어요. 자세한 내용은 한정 변환(qualification conversions)을 참고해요.
cv-한정 타입에 대한 참조나 포인터를 덜 cv-한정된 타입에 대한 참조나 포인터로 변환하려면 const_cast를 써야 해요.
참고 (Notes)
비지역 비-volatile 비템플릿(since C++14)비-inline(since C++17) 변수로, extern으로 선언되지 않은 것의 선언에 쓰인 const 한정자는 그 변수에 내부 연결(internal linkage)을 부여해요. 이는 파일 스코프 const 변수가 외부 연결을 갖는 C와 다르다는 점이에요.
C++ 언어 문법은 mutable을 타입 한정자가 아니라 storage-class-specifier로 취급하지만, 저장 클래스나 연결에는 영향을 주지 않아요.
| 참고 | (since) |
|---|---|
volatile의 일부 용도는 deprecated된다: 내장 증가/감소 연산자의 피연산자로서의 volatile 타입 lvalue; 내장 직접 대입의 왼쪽 피연산자로서의 volatile 타입 lvalue(직접 대입 표현식이 미평가 문맥이거나 폐기된 값 표현식이 아닌 한); 함수 매개변수 타입이나 반환 타입으로서의 volatile 객체 타입; 구조적 바인딩 선언에서의 volatile 한정자. |
(since C++20) |
키워드 (Keywords)
const, volatile, mutable
예제 (Example)
#include <cstdlib>
int main()
{
int n1 = 0; // non-const object
const int n2 = 0; // const object
int const n3 = 0; // const object (same as n2)
volatile int n4 = 0; // volatile object
const struct
{
int n1;
mutable int n2;
} x = {0, 0}; // const object with mutable member
n1 = 1; // OK: modifiable object
// n2 = 2; // error: non-modifiable object
n4 = 3; // OK: treated as a side-effect
// x.n1 = 4; // error: member of a const object is const
x.n2 = 4; // OK: mutable member of a const object isn't const
const int& r1 = n1; // reference to const bound to non-const object
// r1 = 2; // error: attempt to modify through reference to const
const_cast<int&>(r1) = 2; // OK: modifies non-const object n1
const int& r2 = n2; // reference to const bound to const object
// r2 = 2; // error: attempt to modify through reference to const
// const_cast<int&>(r2) = 2; // undefined behavior: attempt to modify const object n2
[](...){}(n3, n4, x, r2); // see also: [[maybe_unused]]
std::system("g++ -O3 -Wa,-adhln ./main.cpp"); // may issue asm on POSIX systems
}
가능한 출력 (Possible output):
# typical machine code produced on an x86_64 platform
# (only the code that contributes to observable side-effects is emitted)
main:
movl $0, -4(%rsp) # volatile int n4 = 0;
movl $3, -4(%rsp) # n4 = 3;
xorl %eax, %eax # return 0 (implicit)
ret
n2는 const라 직접 수정이 안 되지만, n1은 비-const 객체라서 const_cast로 참조를 통해 수정하는 게 허용돼요. 반대로 정말 const 객체인 n2를 const_cast로 수정하면 undefined behavior예요. n4의 volatile 대입은 부수 효과로 취급돼서 최적화로 제거되지 않아요.
더 알아보기 (Learn more)
- C 문서의
const한정자. - C 문서의
volatile한정자.