🗨️ 프로그래밍에서 성능 최적화는 매우 중요한 주제이며 지금 얘기하는 내용들은 체감할 정도의 성능개선이 아니다. 하지만 constexpr과 inline 이 두 키워드의 의미와 사용법에 대해 알게 된다면 기계어 동작 간소화와 오버헤드를 절약하는 이점을 취하고, 기존 레거시 코드에 constexpr과 inline 도구를 잘 못 사용하여 성능을 저하시키고 있는 문제를 개선하게 될지도 모른다. 🤓
constexpr: 컴파일 시간의 마법
📜 C++ core guidelines
F.4: 함수가 컴파일 시간에 평가되어야 한다면 constexpr로 선언하라
constexpr란?
📌 constexpr 발음을 어느 교수님께 배우느냐에 따라 조금씩 다르게 알던데 저는 뭐 '콘스트 익스퍼'라로 부르고 있다.
constexpr은 C++11에서 도입된 키워드로, '상수 표현식(constant expression)'의 줄임말이다. 이름에서 알 수 있듯이 변수 및 함수를 상수로 표현한다는 뜻이며, 특징으로는 일반적인 함수는 런타임에 실행되지만, constexpr 함수는 컴파일 시간에 그 값이 결정될 수 있다.
'결정된다'가 아니라 '결정될 수 있다?' 🤔
결정된다가 아니라 결정될 수 있다는 이유를 예시를 통해 알아보자.
두수 중 큰 값을 리턴하는 constexpr 함수 코드
// 두 수 중 큰 값을 return 하는 템플릿 max 함수
template<typename T>
T max(T lhs, T rhs)
{
return lhs < rhs ? rhs : lhs;
}
// 두 수 중 큰 값을 return 하는 템플릿 constexpr max 함수
template<typename T>
constexpr T max_constexpr(T lhs, T rhs)
{
return lhs < rhs ? rhs : lhs;
}
int main()
{
int ret1 = max(1, 2); // max 함수 호출
// 컴파일 시간에 계산됨
int ret2 = max_constexpr(1, 2); // 2로 컴파일됨
// 런타임에도 사용 가능
int a,b;
int ret3 = max_constexpr(a, b); // 하지만 결과는?
}
둘다 이상 없이 실행은 되지만, max 함수와 max_constexpr 함수에 상수를 넣은 경우와 변수를 넣은 결과를 기계어로 비교해 보자.
max 함수 : max 함수가 호출되어 처리한다.

max_constexpr(1,2) : 컴파일에서 계산된 2의 값을 넣는 것으로 끝!

하지만 max_constexpr(a,b) : max 함수와 동일하게 함수가 호출되어 처리된다.

예시에도 알 수 있듯이 constexpr 함수는 실행되어도 컴파일타임에서 결과를 알 수 없다면 constexpr로 동작하지 않는다.
이외에도 버전별 제약사항을 정리하면 아래와 같다.
constexpr 함수의 제약 사항
- C++11: 단일 return 문만 허용
- C++14: 여러 문장, 지역 변수, 루프, 조건문 허용
- C++17: if constexpr, 람다 표현식에서의 constexpr 지원
- C++20에서는 훨씬 더 많은 기능을 constexpr에서 사용할 수 있게 되었다고 한다.
🗨️ 사실 나는 C++11 수준의 코드만 사용하고 있었는데, 리서치하면서 점점 발전해 오고 있음을 알게 되었다.
claude.ai에게 constexpr함수의 제약사항 완화 변천사를 물어봤는데 관심 있으면 더보기로 확인해 보자.
constexpr 함수의 제약 사항 완화 변천사
C++11에서 처음 소개된 constexpr 함수는 많은 제약이 있었지만, 이후 C++ 표준이 발전하면서 점차 완화되었습니다. 각 버전별 주요 개선 사항을 예시 코드와 함께 설명해 드리겠습니다.
C++11: 초기 constexpr 함수의 제약
C++11에서 constexpr 함수는 매우 제한적이었습니다:
- 함수 본문에 단일 return 문만 허용
- 지역 변수, 루프, 조건문 불가
- 제한된 표현식만 사용 가능
// C++11에서의 constexpr 함수 예시
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1); // 재귀는 가능하지만 단일 return문만 허용
}
// 컴파일 시간에 계산
constexpr int result = factorial(5); // 120
C++14: 주요 제약 완화
C++14에서는 제약 사항이 크게 완화되었습니다:
- 다중 문장 허용
- 지역 변수 사용 가능
- 루프와 조건문 허용
- 단, 가변 크기 배열, try-catch, goto 등은 여전히 금지
// C++14에서 가능해진 constexpr 함수
constexpr int factorial(int n) {
int result = 1; // 지역 변수 허용
for (int i = 1; i <= n; ++i) { // 루프 허용
result *= i;
}
return result;
}
// if문 사용 예시
constexpr int max_value(int a, int b) {
if (a > b) // 조건문 허용
return a;
return b;
}
C++17: 추가 제약 완화
C++17에서는 constexpr 함수 내에서 더 많은 기능을 사용할 수 있게 되었습니다:
- if constexpr 도입 (컴파일 타임 조건부 컴파일)
- 람다 표현식에서 constexpr 사용 가능
// C++17: if constexpr 사용 예시
template <typename T>
constexpr auto get_value(T t) {
if constexpr (std::is_pointer_v<T>) {
return *t; // T가 포인터 타입일 때만 컴파일
} else {
return t; // T가 포인터가 아닐 때만 컴파일
}
}
// C++17: constexpr 람다
constexpr auto squared = [](int n) constexpr {
return n * n;
};
constexpr int result = squared(4); // 16
C++20: 가상 함수 및 try-catch 지원
C++20에서는 더 많은 제약이 완화되었습니다:
- constexpr 가상 함수 허용
- try-catch 허용
- 동적 메모리 할당(new/delete) 사용 가능
- std::vector, std::string 등 일부 표준 라이브러리 컨테이너의 constexpr 지원
// C++20: constexpr 가상 함수
struct Base {
constexpr virtual int getValue() const { return 1; }
};
struct Derived : Base {
constexpr int getValue() const override { return 2; }
};
constexpr int getBaseValue(const Base& b) {
return b.getValue(); // 컴파일 타임에 가상 함수 호출
}
// C++20: constexpr try-catch
constexpr int divide(int a, int b) {
try {
if (b == 0)
throw "Division by zero";
return a / b;
} catch (...) {
return 0;
}
}
// C++20: constexpr 동적 메모리 할당
constexpr auto make_array() {
auto* arr = new int[3]{1, 2, 3};
int sum = arr[0] + arr[1] + arr[2];
delete[] arr;
return sum;
}
constexpr int array_sum = make_array(); // 6
C++23: 추가 개선 사항
C++23에서는 다음과 같은 기능들이 추가되었습니다:
- constexpr에서 thread_local 변수 사용 가능
- constexpr 함수에서 std::unique_ptr, std::shared_ptr 등의 스마트 포인터 사용 가능
- std::expected, std::optional의 constexpr 지원 확대
// C++23: constexpr 스마트 포인터
constexpr int use_smart_pointer() {
std::unique_ptr<int> ptr = std::make_unique<int>(42);
int value = *ptr;
return value;
}
constexpr int smart_ptr_value = use_smart_pointer(); // 42
// C++23: constexpr std::expected 사용
constexpr std::expected<int, std::string> safe_divide(int a, int b) {
if (b == 0)
return std::unexpected("Division by zero");
return a / b;
}
constexpr auto result = safe_divide(10, 2); // contains 5
🗨️ 필살기🧨는 아무 때나 쓸 수 없지만 강력한 한방이 있듯이 제약사항을 고려하여 아래의 장점을 코드 속에 녹여내자.😁
constexpr의 장점
- 컴파일 타임 검증: 컴파일 시 계산되므로 일부 오류를 미리 발견할 수 있다.
- 실행 시간 단축: 계산이 컴파일 시간에 이루어지므로 런타임 오버헤드가 없다.
- 메모리 사용 최적화: 상수로 계산된 값은 런타임에 다시 계산할 필요가 없다.
inline: 함수 호출 오버헤드 제거하기
📜 C++ core guidelines
F.5: 함수가 매우 짧고 수행시간이 중요하다면 inline으로 선언하라
inline이란 무엇인가?
inline 키워드는 컴파일러에게 함수 호출 대신 함수의 본문을 직접 삽입하도록 제안하는 키워드이다.
예시로 내용을 좀 더 알아보자.
inline 함수 예시
// 간단한 inline 함수
inline int max(int a, int b) {
return (a > b) ? a : b;
}
int main() {
int x = 5, y = 10;
int larger = max(x, y); // 컴파일러가 "int larger = (x > y) ? x : y;"로 대체할 수 있음
return 0;
}
일반함수라면 max 함수로 점프해야 하지만 아래코드처럼 max 함수의 수행이 main 함수 안에서 동작이 된다.

이로 인한 이점을 정리해 보면 다음과 같다.
inline의 장점
- 함수 호출 오버헤드 제거: 스택 프레임 설정, 매개변수 전달, 반환 값 처리 등의 오버헤드를 절약할 수 있다.
- 추가 최적화 기회: 함수 본문이 호출 지점에 직접 삽입되면 컴파일러가 더 많은 최적화를 수행할 수 있다.
- 헤더 파일에 함수 정의 가능: inline 함수는 여러 번 정의되어도 링커 오류 - ODR(One Definition Rule) 위반을 방지한다.
반대로 inline에 대한 오해를 부가적으로 정리하면 다음과 같다.
inline에 관한 오해와 진실
- 오해: inline 키워드를 사용하면 항상 함수가 인라인화된다.
- 진실: inline은 단지 제안일 뿐, 컴파일러가 최종 결정을 내린다.
- 오해: 인라인 함수는 항상 더 빠르다.
- 진실: 너무 큰 함수를 인라인화하면 코드 크기가 증가하여 캐시 효율성이 떨어진다.
- 오해: 모든 작은 함수는 inline으로 선언해야 한다.
- 진실: 현대 컴파일러는 inline 키워드 없이도 함수를 자동으로 인라인화하고 있다. (오히려 ODR 위반 방지용으로 더 사용한다.)
constexpr vs inline 차이 정리
비스한 듯 다른 두 키워드는 모두 성능 최적화와 관련 있지만, 다음과 같은 차이점이 있다.
특성 | constexpr | inline |
주요 목적 | 컴파일 시간 계산 | 함수 호출 오버헤드 제거 |
계산 시점 | 컴파일 시간(가능한 경우) | 런타임 |
ODR 위반 방지 | 가능 | 가능 |
상수 표현식에서 사용 | 가능 | 불가능 |
흥미로운 점은 constexpr 함수가 암시적으로 inline이라는 것이다. 즉, constexpr 함수는 컴파일 시간 계산과 함수 인라인화의 장점을 모두 가지고 있다.
알아서 컴파일에 판단하고 동작을 한다면 모든 함수에 constexpr로 만들면 되지 않을까? 🤔
이렇게 하면, 컴파일에서 분석과 최적화 작업을 하느라 컴파일 시간이 증가한다. 그리고 오류 발생 시 복잡하고 이해하기 어려운 오류 메시지가 생성될 수도 있다. 그래서 C++ Core guidelines는 이렇게 권고를 하고 있는 것이다.
📜 C++ core guidelines
F.4: 함수가 컴파일 시간에 평가되어야 한다면 constexpr로 선언하라
F.5: 함수가 매우 짧고 수행시간이 중요하다면 inline으로 선언하라
각각 어떤 상황에서 쓰면 좋을지를 상세히 정리하면 다음과 같다.
constexpr 사용:
- 컴파일 시간에 결과를 알 수 있는 수학적 계산
- 상수 테이블 초기화
- 타입 특성(type traits) 구현
- 메타프로그래밍
- 템플릿 매개변수 계산
inline 사용:
- 매우 작고 자주 호출되는 함수
- 헤더 파일에 정의해야 하는 함수
- 함수 템플릿 구현
- 성능이 중요한 코드 경로에 있는 간단한 함수
이 내용들 중 헤더 파일에 정의해야 하는 함수 예시는 참고 바란다.
constexpr과 inline의 활용 예시
헤더 전용 라이브러리 구현
// math_utils.h
#ifndef MATH_UTILS_H
#define MATH_UTILS_H
namespace math {
// 헤더에 inline 함수 정의
inline double square(double x) {
return x * x;
}
// 컴파일 시간 계산이 가능한 constexpr 함수
constexpr double pi() {
return 3.14159265358979323846;
}
// constexpr 및 inline 함수
constexpr double circle_area(double radius) {
return pi() * square(radius);
}
}
#endif // MATH_UTILS_H
이 헤더 파일은 구현 파일(.cpp) 없이도 여러 소스 파일에서 포함하여 사용할 수 있다.
🗨️ 마치며, constexpr과 inline은 C++ 프로그래밍에서 성능 최적화를 위한 강력한 도구이다. 각각의 키워드가 가진 특성과 목적을 이해하여 좀 더 효율적이고 빠른 코드를 작성하길 바란다.
참고
C++ 핵심 가이드라인 한글화 프로젝트 링크
C++ Core Guidelines 원문 링크
- F.4: If a function might have to be evaluated at compile time, declare it constexpr
- F.5: If a function is very small and time-critical, declare it inline

'L C++' 카테고리의 다른 글
🤔 스마트 포인터(unique_ptr, shared_ptr) 생성 시 make_shared(or make_unique)를 써야 하는 이유? (0) | 2025.03.14 |
---|---|
남들 쓰듯이 쓰는 virtual이라면 고민할 필요 없는 알쓸 virtual 정보 📖 (0) | 2024.10.06 |
직접선언! 낄낄빠빠하자. (0) | 2024.09.09 |
trivial 뜻 : 하찮은, 그렇지만 하찮지 않은 trivial‼ (0) | 2024.08.25 |
컴파일러와 페어 프로그래밍 - 특별 멤버 함수 편 🚀 (0) | 2024.08.15 |