핵심 정리
cpp
#include <optional>
std::optional<int> findScore(bool found) {
if (found) return 100;
return std::nullopt;
}
auto score = findScore(true);
if (score) { // bool 변환으로 존재 확인
std::cout << *score << "\n"; // 역참조로 값 접근
}
int val = score.value_or(0); // 없으면 기본값 0문법
sentinel 값 vs optional — 의도를 타입으로 표현
-1, nullptr, 빈 문자열처럼 "값이 없음"을 나타내는 sentinel 값은 호출자가 특별 처리 규칙을 외워야 합니다. optional은 타입 자체가 "없을 수 있음"을 나타내므로 API가 명확해집니다.
cpp
// ❌ sentinel 방식 — -1이 "없음"이라는 규칙을 호출자가 알아야 함
int findIndex(const std::vector<int>& v, int val) {
for (int i = 0; i < v.size(); ++i)
if (v[i] == val) return i;
return -1; // "없음"을 -1로 표현 — 호출자가 확인 안 하면 버그
}
int idx = findIndex(v, 99);
v[idx]; // ❌ -1 확인 안 하면 UB
// ✅ optional 방식 — 타입이 "없을 수 있음"을 명시
std::optional<int> findIndex(const std::vector<int>& v, int val) {
for (int i = 0; i < v.size(); ++i)
if (v[i] == val) return i;
return std::nullopt;
}
auto idx = findIndex(v, 99);
if (idx) v[*idx]; // ✅ 확인 없이는 접근 어려움생성·접근 — has_value, value, value_or
cpp
std::optional<std::string> name = "Alice";
// 존재 확인
bool ok1 = name.has_value(); // true
bool ok2 = static_cast<bool>(name); // true
if (name) { ... } // 가장 관용적
// 값 접근
std::string s1 = *name; // 역참조 — 없으면 UB
std::string s2 = name.value(); // 없으면 std::bad_optional_access 예외
std::string s3 = name.value_or("unknown"); // 없으면 기본값
// 빈 optional 생성
std::optional<int> empty; // 기본 생성 — 비어 있음
std::optional<int> empty2 = std::nullopt; // 명시적C++23 모나딕 연산 — transform / and_then / or_else
cpp
std::optional<std::string> getUser() { return "alice"; }
std::optional<int> findScore(const std::string& user) {
if (user == "alice") return 95;
return std::nullopt;
}
// transform — 값이 있으면 변환, 없으면 nullopt 전파
auto len = getUser()
.transform([](const std::string& s) { return (int)s.size(); })
.value_or(0); // "alice" → 5
// and_then — 변환 함수 자체가 optional 반환 (flatMap)
auto score = getUser()
.and_then(findScore); // optional<string> → optional<int>
// getUser()가 nullopt이면 and_then도 nullopt
// findScore가 nullopt 반환해도 그대로 전파
// or_else — 없을 때 대안 optional 반환
std::optional<int> getPort() { return std::nullopt; }
auto port = getPort()
.or_else([]{ return std::optional<int>{8080}; })
.value(); // 8080
// C++17 이하에서 수동 체이닝 (중첩 if)
std::optional<int> result;
if (auto user = getUser()) {
if (auto sc = findScore(*user)) {
result = *sc;
}
}체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 값이 없을 수 있는 반환 | std::optional<T> |
| 없음을 명시 | return std::nullopt |
| 없으면 기본값 사용 | opt.value_or(default) |
| 안전한 접근 (예외 원할 때) | opt.value() |
| 존재 여부만 확인 | if (opt) 또는 opt.has_value() |
| 값 변환 (없으면 nullopt 전파) | C++23 opt.transform(f) |
| 변환 함수가 optional 반환 | C++23 opt.and_then(f) |
| 없을 때 대안 optional 제공 | C++23 opt.or_else(f) |
| 오류 원인도 전달해야 할 때 | std::expected (C++23) 또는 예외 |
주의할 점
존재 확인 없이 *opt나 opt.value()로 접근하면 정의되지 않은 동작 또는 예외가 발생합니다.
cpp
std::optional<int> opt = std::nullopt;
// ❌ 존재 확인 없이 역참조 — 정의되지 않은 동작
int val = *opt; // UB
// ❌ value()도 위험 — std::bad_optional_access 예외
int val2 = opt.value(); // 예외 발생
// ✅ 항상 확인 후 접근
if (opt) {
int val = *opt;
}
// ✅ value_or로 안전하게
int val3 = opt.value_or(0);
// ⚠️ optional은 오류 원인을 전달하지 않음
// "왜 없는지"가 중요하면 예외 또는 std::expected 사용참고 링크
1 sources