이번글은 C++11의 constexpr키워드에 대한 글입니다!
constexpr과 상수
constexpr 키워드는 어떤 객체나 함수 앞에 붙일 수 있는 키워드로, 어떤 객체나 함수의 리턴 값을 컴파일 타임에 값을 알 수 있다 라는 의미를 전달합니다.
컴파일러가 컴파일 타임에 어떤 식(표현식)을 결정할 수 있다면 해당 식을 '상수식(constant expression)'이라고 합니다.
여기서 상수는 무엇일까요? 상수는 '변하지 않는 값'입니다.
그래서 선언과 동시에 정의(초기화)해야합니다.
이런 특징을 가지는 상수에는 두 종류가 있습니다.
바로 '컴파일 시간 상수(compile-time constant)', '런타임 상수(runtine constant)'입니다.
런타임 상수의 예부터 보면은
int a;
std::cin >> a;
const int cn = a;
// 또는
int a2 = 10;
const int cn2 = a2;
cn, cn2둘 다 런타임 상수입니다. 프로그램이 실행되면은 그때 한줄한줄씩 상수값이 결정이 됩니다.
그럼 "컴파일 타임 상수"는 뭐냐? 라고 물어보시면
대표적인 예가 정적배열의 크기를 결정할 때 컴파일 타임 상수가 사용됩니다.
int n;
std::cin >> n;
int arr[n];
위처럼 정적 배열 arr을 초기화 할 수 있는 코드를 보신적 있으신가요? 불가능합니다.
정적 배열의 경우 컴파일 타임에 그 크기가 결정되어야 하기 때문입니다.
이렇게 컴파일 타임에 상수값이 결정되기를 원하는 상황일 때
constexpr int n;
int arr[n];
위처럼 constexpr키워드를 사용할 수 있습니다.
constexpr 함수
컴파일 타임 상수를 만들어 낼 때에도 constexpr를 사용할 수 있습니다.
constexpr이 존재하기 이전에는 컴파일 타임 상수 객체를 만드는 것이 어려웠습니다.
어렵다라는 말의 의미는 constexpr이전에는 'TMP 템플릿 메타 프로그래밍'을 통해서 구현할 수 밖에 없었습니다.
template <int N>
struct Fac
{
static const int value = N * Fac<N - 1>::value;
};
template <>
struct Fac<0>
{
static const int value = 1;
};
template <int N>
struct Obj
{
int operator () () { return N; }
};
int main()
{
Obj<Fac<5>::value> obj;
std::cout << obj() << std::endl;
return 0;
}
템플릿에 대한 각 TU는 컴파일 타임에 계산되기 때문에 런타임의 실행속도를 줄일 수 있는 장점이 있습니다.
하지만 반복문을 위와 같은 형태(재귀적)인 형태로 구현하다보니 이해하기 어렵고 난해하다고 합니다. (저도 아직 그렇습니다..ㅎ)
그래서 간단하게 constexpr만 붙여주면 위의 코드가 아래처럼 됩니다.
constexpr int Fac(int n)
{
int total = 1;
for (int i = 1; i <= n; ++i) total *= i;
return total;
}
template <int N>
struct Obj
{
int operator () () { return N; }
};
int main()
{
Obj<Fac(5)> obj;
std::cout << obj() << std::endl;
return 0;
}
TMP와 마찬가지로 컴파일 타임에 계산이 되는 것을 확인 할 수 있습니다.
하지만 주의 해야할 점들이 있습니다.
- goto 사용 X,
- 예외 처리(C++20 부터 가능) X,
- 리터럴 타입이 아닌 변수의 정의,
- 초기화 되지 않은 변수의 정의,
- 실행 중간에 constexpr이 아닌 함수를 호출 X
정도가 있습니다.
const함수 안에서도 비const함수를 호출하면 컴파일이 안된다라는 점을 정확히 알고 계시면 마지막 빼고는 조금 생각해보면 왜 사용이 안되는지 이해가 갈거 같습니다.
또 한가지 특징이 constexpr 함수는 컴파일 타임 상수만을 매개변수로 받지는 않습니다.
constexpr 이 붙은 Fac함수의 인자에 컴파일 타임 상수를 넘겨주게 되면은 Fac의 반환 값은 컴파일 타임 상수가 되고
컴파일 타임 상수가 아닌 일반 (ex int, float 등)자료형을 넣어주게 되면 일반 함수처럼 동작하게 됩니다!
그러면 모든 함수에 constexpr을 붙이는 것이 좋을거같다! 라는 생각이 들기도 하는데요...
왜냐하면
- 컴파일 타임에 계산되므로 실행 시간 성능이 향상됩니다.
- 컴파일 타임에 계산되므로 런타임 오류를 방지할 수 있습니다.
- 컴파일 타임에 계산되므로 컴파일 타임 최적화를 할 수 있습니다.
위와 같은 장점들이 존재하기 때문이죠. 하지만 못 사용하는 쉬운 대표적인 예로 std::vector::push_back 같은 경우는 런타임에 따라 상태가 변경되는 함수입니다. push_back같은 경우에 사용을 못하겠죠.
또한 new/malloc 등 메모리 할당 연산을 포함하는 함수또한 constexpr키워드가 사용불가 합니다. 또한 IO작업을 수행하는 부분도 포함 등등 많은 경우가 존재합니다.
정리하자면
constexpr을 통해 컴파일 타임 상수 객체 선언이 가능하다.
const, constexpr은 다르다.
정도 입니다.
간단하게 constexpr에 대해 알아보았습니다.
감사합니다 :)
참고한 자료
https://en.cppreference.com/w/cpp/language/constexpr
'CPP' 카테고리의 다른 글
[C++] 가변 길이 템플릿과 후행 리턴 타입 (0) | 2023.08.20 |
---|---|
[C++] boost 라이브러리 설치 방법 (0) | 2023.08.17 |
[C++] 헤더파일의 의미와 Build Process (0) | 2023.08.10 |
[C++] stream buffer와 표준 입출력 (0) | 2023.08.02 |
[C++] 전달참조 (0) | 2023.07.31 |
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!