C++ std::optional, 값이 없을 때 예외 없이 다루는 법
C++ std::optional은 값이 있을 수도 없을 수도 있는 C++17 래퍼이며, has_value와 value_or로 빈 상태를 예외 없이 확인합니다. 값이 필요한 경우에만 value()를 선택하고, 빈 상태에서는 기본값 반환이나 조건 분기를 사용합니다.
이 글은 cppreference의 std::optional 문서를 2026-08-27 기준으로 정리한 일반 설명이며, 표준 버전과 구현에 따라 세부 동작은 달라질 수 있습니다.
값이 없을 때를 예외 없이 다루는 이유는?
한 줄 답: 이 타입은 값이 있을 수도 없을 수도 있는 상태를 객체로 나타내므로, 값 부재를 정상적인 분기로 표현할 수 있습니다.
<optional> 헤더에 정의된 이 클래스 템플릿은 C++17부터 제공되며, template< class T > class optional; 형태로 선언됩니다. 이 타입은 포함된 값이 있을 수도 없을 수도 있는 상태를 관리합니다. 따라서 함수가 값을 반환하지 못할 수 있을 때도 반환 형식에 값의 부재를 함께 표현할 수 있습니다.
cppreference는 실패할 수 있는 함수의 반환값을 흔한 사용처로 설명하며, std::pair<T, bool>보다 의도를 명시하기 쉽다고 설명합니다. T와 성공 여부를 별도로 해석하는 대신, 반환 객체 자체가 값 포함 여부를 가집니다. 비용이 큰 객체도 이런 반환 형태로 다룰 수 있다고 설명하지만, 구현별 비용이나 성능 수치는 이 글에서 다루지 않습니다.
빈 상태 자체는 예외가 아닙니다. has_value(), 조건부 bool 변환, value_or()는 빈 상태를 확인하거나 대체 값을 고르는 동작이며, 빈 상태 때문에 std::bad_optional_access를 던지지 않습니다.
has_value와 value_or는 어떻게 쓰나?
한 줄 답: has_value() 또는 if (opt)로 값 존재를 확인하고, 대체 값이 필요하면 value_or(default_value)로 읽습니다.
has_value()는 포함 값이 있으면 true, 없으면 false를 반환합니다. explicit operator bool()도 조건부 문맥에서 같은 값을 제공하므로 if (opt) 형태로 확인할 수 있습니다. 두 함수는 C++17부터 제공되며 noexcept입니다.
std::optional<int> opt;
opt.has_value(); // false
opt = 43;
if (opt)
/* opt.value()로 값에 접근할 수 있습니다. */
opt.reset();
opt.has_value(); // false
value_or(default_value)는 값이 있으면 포함 값을 반환하고, 없으면 전달한 기본값을 반환합니다. 그래서 반환값이 없을 때 사용할 정책이 하나의 표현식에 드러나며, 빈 상태 때문에 bad_optional_access를 던지지 않습니다. value_or를 사용할 때에는 기본값이 포함 값의 타입으로 변환될 수 있고, 필요한 복사 또는 이동 생성 조건을 만족해야 합니다.
maybe_getenv("SHELL").value_or("(none)");
maybe_getenv("MYPWD").value_or("(none)");
실제 반환 내용은 실행 환경에 따라 달라질 수 있으므로 특정 환경 변수 경로를 전제하지 않습니다.
포인터와 무엇이 다른가?
한 줄 답: 포함된 값은 이 래퍼 객체 안에 중첩되므로, 접근 연산자가 있어도 포인터 모델이 아니라 객체 모델입니다.
어떤 인스턴스도 각 시점에 값을 포함하거나 포함하지 않는 둘 중 하나의 상태입니다. 값이 포함되면 그 값은 이 래퍼 객체 안에 중첩됩니다. 그러므로 빈 상태는 널 주소를 가리키는 상태가 아니라, 객체가 값을 포함하지 않는 상태입니다.
operator*와 operator->가 제공되지만, 이 연산자들은 포함 값에 접근하는 문법일 뿐 이 타입을 포인터 타입으로 바꾸지 않습니다. 포인터와 같은 주소·널 여부의 모델로 해석하지 않고, 값이 있는지 먼저 확인하는 객체 모델로 이해해야 합니다. 이 차이는 값의 존재를 표현하는 방식과 접근 실패를 처리하는 방식을 구분하게 합니다.
C++ 기술면접 질문 10가지는 이 글의 값 부재 표현과 별도로 C++ 개념을 점검할 때 참고할 수 있습니다.
value()는 왜 예외를 던지나?
한 줄 답: value()는 값이 있으면 포함 값의 참조를 반환하지만, 없으면 std::bad_optional_access를 던져 접근 실패를 명시합니다.
value()는 값이 있으면 포함 값에 대한 참조를 반환합니다. 값이 없으면 std::bad_optional_access 예외를 던지므로, 호출자가 예외를 처리할 수 있는 접근 실패 신호가 됩니다. 다음 예시는 빈 상태에서 호출한 뒤 값을 대입하면 정상적으로 읽고 쓸 수 있음을 보여줍니다.
std::optional<int> opt = {};
try {
int n = opt.value(); // 예외가 발생합니다.
} catch (const std::bad_optional_access& e) {
// e.what() → "bad optional access"
}
opt = 43;
*opt; // 43
opt.value() = 44;
예외 없이 처리하려는 목적이면 value()를 바로 호출하지 말고 먼저 has_value() 또는 if (opt)로 분기하거나 value_or()를 선택합니다. 반대로 값이 반드시 있어야 하며 접근 실패를 예외로 알리는 계약이라면 value()의 동작을 사용합니다. operator*와 operator->는 빈 상태를 검사하지 않으므로 빈 상태에서 호출하면 정의되지 않은 동작이 발생하며, 정의된 예외가 발생하는 것이 아닙니다.
cppreference는 검사하지 않는 operator*가 value()보다 더 효율적일 수 있다고 설명합니다. 다만 이 설명은 검사 여부의 차이를 말하는 것이므로, 특정 구현의 속도나 예외 비용 수치로 확대하지 않습니다.
disengaged 상태는 무엇인가?
한 줄 답: disengaged는 값이 없는 상태이며, 기본 생성·빈 래퍼 또는 std::nullopt 대입·reset() 호출로 만들 수 있습니다.
disengaged는 값이 없는 상태를 가리키는 용어입니다. std::nullopt는 이 상태를 나타내는 std::nullopt_t 타입의 상수이며, 값이 포함되지 않았음을 명시할 때 사용합니다. 이 상태에서 has_value()는 false를 반환하고 조건부 bool 변환도 거짓이 됩니다.
기본 초기화한 객체, 빈 래퍼 또는 std::nullopt_t로 초기화·대입한 객체는 값을 포함하지 않습니다. reset()을 호출해도 포함된 값이 있으면 그 값이 파괴되고, 객체는 값 미포함 상태로 남습니다. op = {};와 op = nullopt;(일반적인 표기는 op = std::nullopt;)는 모두 객체를 disengaging하는 문법으로 사용할 수 있습니다.
공식 예시의 create와 create2는 값이 있을 때 "Godzilla"를 반환하고, 없을 때 각각 {}와 std::nullopt를 반환합니다. if (auto str = create2(true))는 값 존재를 조건부 bool로 확인한 뒤 *str로 접근하는 형태입니다.
std::optional<std::string> create(bool b)
{
if (b)
return "Godzilla";
return {};
}
auto create2(bool b)
{
return b ? std::optional<std::string>{"Godzilla"} : std::nullopt;
}
create(false).value_or("empty"); // "empty"
if (auto str = create2(true))
*str; // "Godzilla"
FAQ
한 줄 답: 빈 상태 확인, 기본값 반환, 검사 없는 접근의 차이를 구분하면 핵심 사용 판단을 할 수 있습니다.
빈 상태를 다루는 API는 목적에 따라 나누어 선택합니다. 다음 질문과 답변은 값 유무 확인, 기본값 처리, 접근 실패를 구분하는 기준을 정리합니다.
| 질문 | 답변 |
|---|---|
| 값이 있는지 확인하는 방법은 무엇입니까? | has_value()와 if (opt)의 조건부 bool 변환은 모두 값이 있으면 true, 없으면 false를 반환합니다. |
| 빈 상태에서 기본값을 사용하려면 무엇을 선택합니까? | value_or(default_value)를 사용하면 값이 있을 때 포함 값을, 없을 때 기본값을 반환하며 빈 상태 때문에 bad_optional_access를 던지지 않습니다. |
빈 상태에서 value()를 호출하면 어떻게 됩니까? | std::bad_optional_access 예외를 던집니다. |
빈 상태에서 *opt 또는 opt->member를 호출하면 어떻게 됩니까? | 빈 상태 검사가 없으므로 정의되지 않은 동작입니다. 먼저 값 유무를 확인하거나 value() 또는 value_or()를 선택해야 합니다. |
모든 타입을 T로 사용할 수 있습니까? | 참조·함수·배열·cv 한정 void, 그리고 nullopt_t·in_place_t는 인스턴스화할 수 없습니다. T는 Destructible 요구사항을 만족해야 합니다. |
출처
한 줄 답: 포함 값의 상태와 객체 모델, 관찰자, 빈 상태 표기는 모두 2026-08-27 기준 cppreference 문서로 확인합니다.
본문의 표준 동작과 코드 예시는 아래의 공식 cppreference 문서를 바탕으로 정리합니다. 문서의 세부 동작은 표준 버전과 구현에 따라 달라질 수 있으므로 실제 사용 환경에서 다시 확인해야 합니다.