constinit 지정자
constinit 지정자 (constinit specifier)
constexpr 변수는 값이 컴파일 타임에 계산되지만 동시에 const가 돼서 나중에 바꿀 수 없게 돼요. 그런데 정적·스레드 저장 기간 변수에 "초기화만 컴파일 타임에 하되, 그 값은 나중에 바꿀 수 있게" 하고 싶을 때가 있어요. constinit 지정자가 바로 그런 용도예요. 이 페이지에서 constinit의 규칙을 살펴볼게요.
출처: cppreference
본문
constinit 지정자는 정적(static) 또는 스레드(thread) 저장 기간을 가진 변수를 선언해요.
constinit 지정자는 구조화 바인딩(structured binding) 선언에도 적용할 수 있어요. 이 경우 constinit은 그 선언이 도입하는 고유 이름 변수에도 적용돼요. (since C++26)
변수가 constinit으로 선언되면 그 초기화 선언에도 constinit이 적용되어야 해요. constinit으로 선언된 변수가 동적 초기화(정적 초기화로 수행되더라도)를 가진다면 프로그램은 ill-formed예요.
초기화 선언 지점에서 어떤 constinit 선언에도 도달할 수 없다면, 프로그램은 진단 없이 ill-formed예요(no diagnostic required).
constinit은 constexpr과 함께 쓸 수 없어요. 선언된 변수가 참조일 때 constinit은 constexpr과 동등해요. 선언된 변수가 객체일 때 constexpr은 그 객체가 정적 초기화와 상수 파괴를 가져야 하고 객체를 const로 한정하라고 강제하지만, constinit은 상수 파괴나 const 한정을 강제하지 않아요. 그래서 constexpr 생성자를 가지면서 constexpr 소멸자는 없는 타입(예: std::shared_ptr<T>)의 객체는 constexpr로는 선언할 수 없지만 constinit으로는 선언할 수 있어요.
const char* g() { return "dynamic initialization"; }
constexpr const char* f(bool p) { return p ? "constant initializer" : g(); }
constinit const char* c = f(true); // OK
// constinit const char* d = f(false); // error
constinit은 초기화하지 않는 선언에서도 쓸 수 있어요. 그렇게 하면 thread_local 변수가 이미 초기화됐다는 걸 컴파일러에게 알려서, 그렇지 않았을 때 숨은 가드 변수(hidden guard variable)가 부과하던 오버헤드를 줄여줘요.
extern thread_local constinit int x;
int f() { return x; } // no check of a guard variable needed
참고 (Notes)
피처 테스트 매크로 (Feature-test macro)
| 매크로 | 값 | 표준 | 피처 |
|---|---|---|---|
__cpp_constinit |
201907L | (C++20) | constinit |
키워드 (Keywords)
constinit
예제 (Example)
#include <cassert>
constexpr int square(int i)
{
return i * i;
}
int twice(int i)
{
return i + i;
}
constinit int sq = square(2); // OK: initialization is done at compile time
// constinit int x_x = twice(2); // Error: compile time initializer required
int square_4_gen()
{
static constinit int pow = square(4);
// constinit int prev = pow; // Error: constinit can only be applied to a
// variable with static or thread storage duration
int prev = pow;
pow = pow * pow;
return prev;
}
int main()
{
assert(sq == 4);
sq = twice(1); // Unlike constexpr this value can be changed later at runtime
assert(sq == 2);
assert(square_4_gen() == 16);
assert(square_4_gen() == 256);
assert(square_4_gen() == 65536);
}
결함 보고 (Defect reports)
다음 동작 변경 결함 보고는 이전에 발표된 C++ 표준에 소급 적용됐어요.
| DR | 적용 대상 | 발표된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 2543 | C++20 | constinit으로 선언된 변수가 정적 초기화의 일부로 동적 초기화되는 경우 동작이 불명확했음 |
이 경우 프로그램은 ill-formed |
더 알아보기
- consteval 지정자 (C++20) — 함수가 immediate 함수, 즉 함수에 대한 모든 호출이 반드시 상수 평가여야 함을 지정해요.
- constexpr 지정자 (C++11) — 변수나 함수의 값을 컴파일 타임에 계산할 수 있음을 지정해요.
- constant expression — 컴파일 타임에 평가할 수 있는 표현식을 정의해요.
- constant initialization — 정적 변수의 초기 값을 컴파일 타임 상수로 설정해요.
- zero initialization — 객체의 초기 값을 0으로 설정해요.