안녕하세요. 이번 글에서는 C++17에서 추가된 std::optional에 대해 알아보겠습니다.
함수가 값을 찾지 못했을 때 무엇을 돌려줄지는 늘 고민되는 부분입니다. -1이나 nullptr 같은 특수한 값에 의미를 맡기면 호출하는 쪽에서 약속을 외워야 하고, 실수하기도 쉽습니다. std::optional은 “값이 있을 수도 있고 없을 수도 있다”는 사실을 타입으로 표현해 이 문제를 해결합니다.
헤더와 기본 사용법
헤더는 <optional>이며 C++17 이상에서 사용할 수 있습니다. 주요 멤버는 다음과 같습니다.
| 멤버 | 설명 |
|---|---|
| has_value(), operator bool | 값이 들어 있는지 확인 |
| value() | 값을 반환, 비어 있으면 std::bad_optional_access 예외 |
| operator*, operator-> | 값에 접근, 비어 있으면 미정의 동작 |
| value_or(기본값) | 비어 있으면 기본값을 반환 |
| reset() | 값을 비움 |
| emplace(…) | 값을 제자리에서 생성 |
| std::nullopt | “값 없음”을 나타내는 상수 |
before / after : 특수 값 대신 optional
먼저 -1을 “찾지 못함”으로 쓰는 기존 방식입니다.
// before : -1이 "없음"이라는 약속을 호출하는 쪽이 알아야 합니다.
int FindIndex(const std::vector<int>& v, int target)
{
for (size_t i = 0; i < v.size(); ++i)
if (v[i] == target)
return static_cast<int>(i);
return -1;
}
같은 함수를 optional로 바꾸면 반환 타입만 보아도 실패할 수 있음을 알 수 있습니다.
#include <optional>
#include <vector>
#include <iostream>
// after : 값이 없을 수 있다는 사실이 타입에 드러납니다.
std::optional<size_t> FindIndex(const std::vector<int>& v, int target)
{
for (size_t i = 0; i < v.size(); ++i)
if (v[i] == target)
return i;
return std::nullopt;
}
int main()
{
std::vector<int> v{ 10, 20, 30 };
if (auto idx = FindIndex(v, 20))
std::cout << "index = " << *idx << '\n'; // index = 1
else
std::cout << "not found\n";
std::cout << FindIndex(v, 99).value_or(0) << '\n'; // 0
}
값 접근 방법
value()는 비어 있을 때 예외를 던지고, operator*는 검사하지 않으므로 반드시 값이 있는지 먼저 확인한 뒤 써야 합니다.
std::optional<std::string> name;
std::cout << name.has_value() << '\n'; // 0
std::cout << name.value_or("unknown") << '\n'; // unknown
name = "Alice"; // 값 대입
std::cout << *name << '\n'; // Alice
std::cout << name->size() << '\n'; // 5
name.reset(); // 다시 비움
try
{
std::cout << name.value(); // 예외 발생
}
catch (const std::bad_optional_access& e)
{
std::cout << "empty: " << e.what() << '\n';
}
주의할 점
- optional은 값을 내부에 직접 보관하므로 동적 할당이 없습니다. 대신 객체 크기가 값 크기에 bool 하나만큼(정렬 포함) 더 커집니다.
- 참조는 담을 수 없습니다. std::optional<T&>는 만들 수 없으며, 필요하면 std::optional<std::reference_wrapper<T>>나 포인터를 씁니다.
- value_or는 기본값을 항상 평가합니다. 기본값을 만드는 비용이 크다면 if 문으로 분기하는 편이 낫습니다.
- “값 없음”이 오류의 이유까지 알려 주지는 않습니다. 실패 원인이 필요하다면 예외나 오류 코드, C++23의 std::expected를 고려합니다.
정리
std::optional은 값이 없을 수 있음을 타입으로 드러내어 특수 값에 의존하던 코드를 더 안전하게 만들어 줍니다. 값 접근 전에는 has_value()나 if 조건으로 확인하고, 기본값이 있다면 value_or를 쓰면 간결합니다.
이상으로 std::optional에 대해 간단히 알아보았습니다.