https://en.cppreference.com/cpp/utility/expected
std::expected - cppreference.com
template< class T, class E > class expected; (1) (since C++23) template< class T, class E > requires std::is_void_v class expected ; (2) (since C++23) The class template std::expected provides a way to represent either of two values: an expected value of t
en.cppreference.com
이번 포스트에서는 C++23에 포함된 std::expected를 활용한 에러 처리 코드를 소개한다.
1. Expected 장점
std::expected는 성공했다면 결과값, 실패했을 때의 에러 이유 중 하나를 안전하게 전달한다.
기존까지 여러 에러처리방식이 있었지만 각각 단점이 존재했다.
기존까지 존재하는 함수실행후 에러처리 방식은 아래와 같다.
| 에러 코드 반환 (int 등) | 함수가 0이나 -1을 리턴 | 진짜 결과값과 에러 코드를 혼동하기 쉽고, 왜 실패했는지 구체적인 이유를 알기 어려움 |
| 예외 던지기 (try-catch) | throw로 에러 전파 | 코드가 복잡해지고, 성능 저하(오버헤드)가 발생할 수 있음 |
| std::optional | 값이 있으면 값 반환, 없으면 빈 값(std::nullopt) 반환 | 값이 왜 없는지(에러의 이유)를 알 수 없음 ("그냥 실패했구나" 외에는 정보가 없음) |
위와 같은 단점에서 나아가
expected를 사용하게되면, optional보다 한단계 나아가서 왜 실패했는지에 대한 구체적인 에러타입을 담을 수 있다.
2. 활용사례
필자의 경우 사이드 프로젝트의 로그인 로직에서 user 오브젝트를 할당해주는 결과값과
'로그인 실패' 라는 케이스를 구분하는데에서 해당 기능을 적용해 보았다.
template <typename T>
using expected = std::expected<T, error>;
auto user_manager::user_login(session_s_ptr_t session, std::wstring_view user_name) -> fw::expected<user_s_ptr_t>
{
auto error = fw::error{};
ASSERT_RETURN_VALUE(session != nullptr, fw::unexpected(error::code::session_invalid));
if (!is_valid_user_name(user_name))
{
FLOG_WARN("user_login :: invalid user_name length({}) session({})", user_name.size(), session->get_index());
return fw::unexpected(error::code::user_name_invalid);
}
if (find_user(session) != nullptr)
{
FLOG_WARN("user_login :: already login session({})", session->get_index());
return fw::unexpected(error::code::user_already_login);
}
auto sql = fw::get_sql<user_sql>();
ASSERT_RETURN_VALUE(sql != nullptr, fw::unexpected(error::code::sql_stmt_invalid));
user_no_t user_no{};
error = sql->user_login(user_name, user_no);
ASSERT_RETURN_VALUE(!error, fw::unexpected(error));
ASSERT_RETURN_VALUE(user_no != user_no_t{}, fw::unexpected(error::code::user_login_fail));
auto user = user_pool_.AcquireObject();
ASSERT_RETURN_VALUE(nullptr != user, fw::unexpected(error::code::object_acquire_fail));
user->set_session(session);
user->set_user_no(user_no);
auto [it, inserted] = user_list_.insert(user);
ASSERT_RETURN_VALUE(inserted, fw::unexpected(error::code::user_already_login));
return *it;
}
3. 내부 구현 및 성능
내부적으로 std::variant<T,E>와 매우 유사한 구조다.
- 성공 데이터(T)와 에러 데이터(E)는 크기가 크거나 타입이 다를 수 있으므로, 메모리를 공유하는 union 공간에 저장된다.
- RVO및 이동 연산(Move)이 적용되어 복사 비용이 없다.
'Language > C++' 카테고리의 다른 글
| std::weak_ptr를 통한 shared_ptr 생성은 멀티스레드 환경에서 안전한가? (0) | 2026.01.24 |
|---|---|
| Variant 초기화의 중요성 bad_variant_access (0) | 2022.06.03 |
| 상수 멤버함수의 설계 방법 (0) | 2022.03.02 |
| 참조 전달 문법 std::move, std::forward (0) | 2022.01.13 |
| 이동생성자와 보편참조법(universal ref) (0) | 2022.01.07 |