[Modern C++] 항목 15: 가능하면 항상 constexpr을 사용하라
카테고리: Cpp
이 글은 아래의 책을 정리하였습니다. 이펙티브 모던 C++, 스콧 마이어스 저자, 류광 번역
가독성이 떨어지는 직역들을 수정하며 정리하였습니다. e.g. 연역 → 추론, 중복적재 → 오버로딩
📦 3. 현대적 C++에 적응하기
👉🏻 항목 15: 가능하면 항상 constexpr을 사용하라
🔍 constexpr이란?
constexpr을 객체에 적용했을 때는 본질적으로const의 강화된 버전처럼 작용하지만, 함수에 적용했을 때는 상당히 다른 의미로 작용한다.constexpr객체: 실제로const이며, 그 값은 실제로 컴파일 시점에서 알려진다.- 컴파일 시점에서 알려지는 값들은 읽기 전용 메모리에 배치될 수 있다.
constexpr함수의 결과가 반드시const인 것은 아니며, 그 값이 반드시 컴파일 시점에서 알려진다는 보장도 없다. 이는 표준의 결함이 아니라 의도된 기능이다.
📦 constexpr 객체
int sz; // 비constexpr 변수
constexpr auto arraySize1 = sz; // ❌ 오류! sz의 값이 컴파일 도중에 알려지지 않음
std::array<int, sz> data1; // ❌ 오류! 같은 문제
constexpr auto arraySize2 = 10; // ✅ OK, 10은 확실히 컴파일 시점 상수
std::array<int, arraySize2> data2; // ✅ OK, arraySize2는 constexpr 객체
const가constexpr과 동일한 보장을 제공하지는 않는다.const객체가 반드시 컴파일 시점에서 알려지는 값으로 초기화되지는 않기 때문이다.
const auto arraySize = sz; // OK, arraySize는 sz의 const 복사본
std::array<int, arraySize> data; // ❌ 오류! arraySize의 값은 컴파일 시점에서 알려지지 않음
모든
constexpr객체는const이지만, 모든const객체가constexpr인 것은 아니다. 어떤 변수의 값을 반드시 컴파일 시점 상수를 요구하는 문맥에서 사용할 수 있어야 한다면, 선택할 도구는const가 아니라constexpr이다.
⚙️ constexpr 함수
constexpr 함수는 컴파일 시점 상수를 인수로 호출하는 경우에는 컴파일 시점 결과를 산출하고, 실행시점 값으로 호출하면 일반 함수처럼 작동한다.
constexpr // pow는 결코 예외를 던지지
int pow(int base, int exp) noexcept // 않는 constexpr 함수
{
… // 구현은 나중에
}
constexpr auto numConds = 5; // 조건들의 개수
std::array<int, pow(3, numConds)> results; // results는 3^numConds 개의 요소들을 담는다
pow앞에constexpr이 있다고 해서pow가 반드시const값을 돌려주는 것은 아니다.base나exp중 하나라도 컴파일 시점 상수가 아니면pow의 결과가 실행시점에서 계산될 수 있다.
auto base = readFromDB("base"); // 실행시점에서 값들을 구한다
auto exp = readFromDB("exponent");
auto baseToExp = pow(base, exp); // 실행시점에서 pow 함수를 호출한다
C++11 vs C++14 제약:
- C++11:
constexpr함수는 실행 가능 문장이 많아야 하나(return 문)이어야 한다. 조건부 연산자(?:)나 재귀를 활용해 표현력을 확장할 수 있다.
// C++11
constexpr int pow(int base, int exp) noexcept
{
return (exp == 0 ? 1 : base * pow(base, exp - 1));
}
- C++14:
constexpr함수에 대한 제약이 상당히 느슨해져서 일반적인 제어 흐름도 허용된다.
// C++14
constexpr int pow(int base, int exp) noexcept
{
auto result = 1;
for (int i = 0; i < exp; ++i) result *= base;
return result;
}
constexpr함수는 반드시 리터럴 타입(literal type)들을 받고 돌려주어야 한다.- 리터럴 타입: 컴파일 도중에 값을 결정할 수 있는 타입. C++11에서
void를 제외한 모든 내장 타입이 해당된다. - 생성자와 적절한 멤버 함수들이
constexpr인 사용자 타입도 리터럴 타입이 될 수 있다.
- 리터럴 타입: 컴파일 도중에 값을 결정할 수 있는 타입. C++11에서
🏗️ constexpr과 사용자 정의 타입
class Point {
public:
constexpr Point(double xVal = 0, double yVal = 0) noexcept
: x(xVal), y(yVal)
{}
constexpr double xValue() const noexcept { return x; }
constexpr double yValue() const noexcept { return y; }
void setX(double newX) noexcept { x = newX; }
void setY(double newY) noexcept { y = newY; }
private:
double x, y;
};
Point의 생성자를constexpr로 선언할 수 있는 이유는, 주어진 인수들이 컴파일 시점에서 알려진다면 생성된Point객체의 멤버 값들도 컴파일 시점에서 알려질 수 있기 때문이다.
constexpr Point p1(9.4, 27.7); // ✅ constexpr 생성자가 컴파일 시점에서 "실행됨"
constexpr Point p2(28.8, 5.3); // ✅ 역시 OK
constexprPoint객체에 대해xValue()와yValue()조회 함수를 호출하면, 그 결과를 이용해서 또 다른constexpr객체를 초기화하는constexpr함수를 작성하는 것이 가능하다.
constexpr
Point midpoint(const Point& p1, const Point& p2) noexcept
{
return { (p1.xValue() + p2.xValue()) / 2, // constexpr 멤버
(p1.yValue() + p2.yValue()) / 2 }; // 함수들을 호출
}
constexpr auto mid = midpoint(p1, p2); // constexpr 함수의 결과를 이용해서
// constexpr 객체를 초기화한다
C++14에서의 setter:
constexpr void setX(double newX) noexcept // C++14
{ x = newX; }
constexpr void setY(double newY) noexcept // C++14
{ y = newY; }
- C++11에서는 두 가지 제약 때문에
setX와setY를constexpr로 선언할 수 없었다.- 첫째,
constexpr멤버 함수는 암묵적으로const로 선언된다. - 둘째, 반환 타입이
void인데, C++11에서void는 리터럴 타입이 아니다. - C++14에서는 두 제약 모두 사라졌다.
- 첫째,
// 원점을 기준으로 p와 대칭인 Point 객체를 돌려준다(C++14)
constexpr Point reflection(const Point& p) noexcept
{
Point result; // 비const Point를 생성
result.setX(-p.xValue()); // 그 Point의 x와 y를 설정
result.setY(-p.yValue());
return result; // 그 복사본을 반환
}
constexpr Point p1(9.4, 27.7);
constexpr Point p2(28.8, 5.3);
constexpr auto mid = midpoint(p1, p2);
constexpr auto reflectedMid =
reflection(mid); // reflectedMid의 값은 (-19.1, -16.5)이다;
// 이는 컴파일 도중에 알려진다
🧐 정리
constexpr객체는const이며, 컴파일 도중에 알려지는 값들로 초기화된다.constexpr함수는 그 값이 컴파일 도중에 알려지는 인수들로 호출하는 경우에는 컴파일 시점 결과를 산출한다.constexpr객체나 함수는 비constexpr객체나 함수보다 광범위한 문맥에서 사용할 수 있다.constexpr은 객체나 함수의 인터페이스의 일부다.constexpr을 나중에 제거하면 클라이언트 코드가 깨질 수 있다.
댓글남기기