C++23에서는 코드의 안정성과 오류 처리를 향상시키기 위한 새로운 기능으로 std::expected가 도입되었습니다. 이번 글에서는 std::expected의 개념과 사용법, 그리고 이전 버전과 비교하여 어떻게 개선되었는지 알아보겠습니다.
std::expected란 무엇인가요?
std::expected는 함수의 반환값으로 정상적인 결과와 오류 정보를 함께 전달할 수 있는 객체입니다. 이를 통해 예외를 사용하지 않고도 함수의 실패를 표현할 수 있으며, 코드의 가독성과 안정성을 높일 수 있습니다. std::expected는 템플릿 클래스로, 성공 시의 값 타입과 오류 타입을 지정할 수 있습니다.
이전 버전에서는 어떻게 했나요?
C++23 이전에는 함수의 오류 처리를 위해 주로 다음과 같은 방법을 사용했습니다.
1. 예외(Exception) 사용
int divide(int numerator, int denominator) {
if (denominator == 0) {
throw std::runtime_error("0으로 나눌 수 없습니다.");
}
return numerator / denominator;
}
int main() {
try {
int result = divide(10, 0);
} catch (const std::exception& e) {
std::cerr << "오류 발생: " << e.what() << '\n';
}
return 0;
}
- 문제점:
- 예외 처리는 런타임 오버헤드가 발생할 수 있습니다.
- 예외 안전성(Exception Safety)을 보장하기 위해 코드가 복잡해질 수 있습니다.
- 일부 시스템에서는 예외 사용이 제한적이거나 비권장됩니다.
2. std::optional 사용
std::optional<int> divide(int numerator, int denominator) {
if (denominator == 0) {
return std::nullopt;
}
return numerator / denominator;
}
int main() {
auto result = divide(10, 0);
if (result.has_value()) {
std::cout << "결과: " << result.value() << '\n';
} else {
std::cout << "오류: 0으로 나눌 수 없습니다.\n";
}
return 0;
}
- 문제점:
- 오류에 대한 추가적인 정보 제공이 어렵습니다.
- 오류 원인을 상세히 전달하기 위해서는 별도의 메커니즘이 필요합니다.
C++23의 std::expected를 사용한 개선
std::expected를 사용하면 함수의 반환값에 성공 시의 값과 실패 시의 오류 정보를 함께 담을 수 있습니다.
예제: std::expected 사용
#include <expected>
#include <string>
std::expected<int, std::string> divide(int numerator, int denominator) {
if (denominator == 0) {
return std::unexpected("0으로 나눌 수 없습니다.");
}
return numerator / denominator;
}
int main() {
auto result = divide(10, 0);
if (result) {
std::cout << "결과: " << result.value() << '\n';
} else {
std::cout << "오류: " << result.error() << '\n';
}
return 0;
}
- std::expected<int, std::string>은 성공 시 int 타입의 값, 실패 시 std::string 타입의 오류 메시지를 가집니다.
- result가 참(truthy)이면 성공, 거짓(falsy)이면 실패로 간주합니다.
- 오류 정보를 상세히 전달할 수 있습니다.
어떻게 좋아졌나요?
- 오류 정보 전달 강화: 실패 시 상세한 오류 정보를 함께 반환할 수 있습니다.
- 예외 사용 없이 오류 처리: 예외를 사용하지 않고도 함수의 실패를 표현할 수 있어 런타임 오버헤드를 줄일 수 있습니다.
- 가독성 향상: 코드 흐름이 명확해지고, 오류 처리 로직이 간결해집니다.
- 함수 합성의 용이성: 여러 함수를 조합할 때 std::expected를 활용하여 에러 전파를 쉽게 할 수 있습니다.
상세한 예제와 비교
1. 여러 단계의 함수 호출에서의 오류 전파
이전 방식
std::optional<int> readValue() {
// 파일에서 값을 읽어옴
return std::nullopt; // 오류 발생 시
}
std::optional<int> processValue(int value) {
// 값 처리
return std::nullopt; // 오류 발생 시
}
int main() {
auto value = readValue();
if (!value.has_value()) {
std::cerr << "값을 읽어오는 데 실패했습니다.\n";
return 1;
}
auto result = processValue(value.value());
if (!result.has_value()) {
std::cerr << "값을 처리하는 데 실패했습니다.\n";
return 1;
}
std::cout << "처리 결과: " << result.value() << '\n';
return 0;
}
- 문제점:
- 각 단계마다 오류 체크 코드가 필요합니다.
- 오류 원인에 대한 상세 정보 전달이 어렵습니다.
C++23 방식
#include <expected>
#include <string>
std::expected<int, std::string> readValue() {
// 파일에서 값을 읽어옴
return std::unexpected("파일을 열 수 없습니다."); // 오류 발생 시
}
std::expected<int, std::string> processValue(int value) {
// 값 처리
return std::unexpected("값이 유효하지 않습니다."); // 오류 발생 시
}
int main() {
auto value = readValue();
if (!value) {
std::cerr << "오류: " << value.error() << '\n';
return 1;
}
auto result = processValue(value.value());
if (!result) {
std::cerr << "오류: " << result.error() << '\n';
return 1;
}
std::cout << "처리 결과: " << result.value() << '\n';
return 0;
}
- 각 함수에서 오류 메시지를 명확하게 전달할 수 있습니다.
- 오류 처리가 일관되고 간결합니다.
2. std::expected와 함수 합성
#include <expected>
#include <string>
std::expected<int, std::string> step1() {
// ...
}
std::expected<int, std::string> step2(int value) {
// ...
}
std::expected<int, std::string> step3(int value) {
// ...
}
int main() {
auto result = step1()
.and_then(step2)
.and_then(step3);
if (result) {
std::cout << "최종 결과: " << result.value() << '\n';
} else {
std::cerr << "오류: " << result.error() << '\n';
}
return 0;
}
- and_then을 사용하여 함수를 체인처럼 연결할 수 있습니다.
- 중간에 오류가 발생하면 자동으로 전파됩니다.
주의 사항
- 오류 타입의 선택: 오류 정보를 표현하기 위해 적절한 타입을 선택해야 합니다. std::string, std::error_code 등을 사용할 수 있습니다.
- std::expected와 예외의 조합: 예외를 사용하는 코드와 혼용할 때는 주의가 필요합니다. 일관된 오류 처리 방식을 선택하는 것이 좋습니다.
- 헤더 파일 포함: std::expected를 사용하려면 <expected> 헤더를 포함해야 합니다.
요약
C++23의 std::expected는 함수의 반환값에 성공과 실패를 모두 표현할 수 있는 강력한 도구입니다. 이전에는 예외나 std::optional을 사용하여 오류 처리를 했지만, std::expected를 통해 타입 안전하고 상세한 오류 정보 전달이 가능해졌습니다. 이를 통해 코드의 안정성과 가독성이 향상되며, 함수 합성을 통한 에러 전파가 용이해집니다.
참고 자료:
반응형
'개발 이야기 > C++' 카테고리의 다른 글
[C++23 새기능 소개] deducing this (0) | 2024.12.08 |
---|---|
[C++23 새기능 소개] std::mdspan (0) | 2024.12.08 |
[C++23 새기능 소개] std::stacktrace 라이브러리 (0) | 2024.12.08 |
[C++23 새기능 소개] std::print와 std::println 함수 (0) | 2024.12.08 |
[C++20 새기능 소개] 비타입 템플릿 매개변수에 auto사용 (0) | 2024.12.08 |