[C++] 컴파일 상수 constexpr

1. constexpr이란?

constexpr은 C++11에서 도입된 키워드로, 컴파일 타임에 상수를 만들어 준다고 한다.

 

const라는 키워드도 상수를 만들 수 있지만, constexpr은 변수 뿐만 아니라 함수, 클래스 생성자에도 적용이 가능하다!

 

또한, const 키워드는 컴파일 상수 / 런타임 상수 2가지로 작동 가능하지만 constexpr은 컴파일 상수로만 기능한다.

 

2. constexpr 변수

#include <iostream>

int main(){
    int n = 10;
    
    const int constN1 = 100; // OK, initialized at compile-time
    const int constN2 = n; // OK, intialized at run-time
    
    constexpr int constExprN1 = 100; // OK, initialized at compile-time
    //constexpr int constExprN2 = n; // Error, initialized at run-time
    return 0;
}

 

3. constexpr 함수

constexpr 함수는 함수의 반환 값을 컴파일 타임에 계산할 수 있다는 특징이 있다.

 

constexpr 함수에는 다음과 같은 규칙이 적용된다.

  • constexpr 함수는 리터럴 형식만 사용하고 반환해야 한다.
  • constexpr 함수는 재귀적일 수  있다.
  • (C++20 이전) constexpr 함수는 가상일 수 없으며, 바깥쪽 클래스에 가상 기본 클래스가 있는 경우 생성자는 constexpr로 정의될 수 없다.
  • (C++20 이상) constexpr 함수가 가상일 수 있다.
  • 본문은 = default 또는 = delete로 정의할 수 있다.
  • 본문에는 goto 문이나 try 블록이 포함될 수 없다.
  • ...
#include <iostream>

constexpr int fact(int n){
    if(n<0) return 0;
    if(n==0) return 1;
    return n * fact(n-1);
}

int main(){
    constexpr int n = fact(5);
}

 

근데 함수의 반환 값이 런타임에 계산되는 것과 컴파일 타임에 계산되는 것, 둘의 차이가 어떤 것인지 궁금했다.

 

https://nx006.tistory.com/22

 

[모던 C++] 컴파일 상수 constexpr

constexpr이란 constexpr은 C++11부터 지원되기 시작한 컴파일 상수이다. 11부터 시작해서 23에 이르기까지 constexpr은 그 지원 범위를 점차 늘려왔고, 많은 C++ 프로그래머로부터 각광을 받았다고 한다.

nx006.tistory.com

위 블로그 내용에 따르면, 어셈블리어 코드로 확인했을 때 확연히 차이나는 것을 볼 수가 있었다.

 

실제 위의 코드를 https://godbolt.org/에서 확인해본 결과, 아래와 같이 차이가 났었다.

// const 키워드 사용
// CPP
#include <iostream>

const int fact(int n){
    if(n<0) return 0;
    if(n==0) return 1;
    return n * fact(n-1);
}

int main(){
    const int n = fact(5);
}

// Assembly
fact(int):
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     DWORD PTR [rbp-4], edi
        cmp     DWORD PTR [rbp-4], 0
        jns     .L2
        mov     eax, 0
        jmp     .L3
.L2:
        cmp     DWORD PTR [rbp-4], 0
        jne     .L4
        mov     eax, 1
        jmp     .L3
.L4:
        mov     eax, DWORD PTR [rbp-4]
        sub     eax, 1
        mov     edi, eax
        call    fact(int)
        imul    eax, DWORD PTR [rbp-4]
.L3:
        leave
        ret
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     edi, 5
        call    fact(int)
        mov     DWORD PTR [rbp-4], eax
        mov     eax, 0
        leave
        ret
// constexpr 키워드 사용
// CPP
#include <iostream>

constexpr int fact(int n){
    if(n<0) return 0;
    if(n==0) return 1;
    return n * fact(n-1);
}

int main(){
    constexpr int n = fact(5);
}

// Assembly
main:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], 120
        mov     eax, 0
        pop     rbp
        ret

 

단순히 확인해봐도 큰 차이가 보인다.

 

컴파일 시간이 오래 걸린다는 단점이 있지만, 런타임에 계산하는 것보다는 훨 나아보이는 방식이다.

 

결과 값이 수시로 바뀌는 경우가 아니라면 constexpr를 사용하는 것이 성능 상의 큰 도움이 될 것 같다.

 

 

References