C++ 초급 - 12. 최신 C++ 기능 소개 (5 - C++23: std::expected 기본 개념)

2025. 2. 22. 18:20프로그래밍 언어/C++

📌 12.5 C++23: std::expected 기본 개념

C++23에서는 예외 처리의 새로운 대안으로 std::expected가 도입되었다.
이를 활용하면 오류를 예외가 아닌 값으로 반환하는 방식으로 더 안전하고 예측 가능한 오류 처리가 가능하다.


📌 1. std::expected를 활용한 예외 처리 대체 방법

🔹 (1) 기존 예외 처리 (try-catch)의 문제점

C++에서는 일반적으로 예외(try-catch 블록)를 사용하여 오류를 처리하지만, 예외 기반 오류 처리는 다음과 같은 문제점이 존재한다.

🔹 문제점

  1. 오류를 throw 하면, 제어 흐름을 추적하기 어렵다.
  2. 예외 처리는 성능 오버헤드가 발생할 수 있다.
  3. 코드의 가독성이 떨어지고, 유지보수가 어려워질 수 있다.

💡 예제: 기존 예외 처리 방식 (try-catch)

#include <iostream>
#include <stdexcept>

int divide(int a, int b) {
    if (b == 0) {
        throw std::runtime_error("0으로 나눌 수 없습니다!");
    }
    return a / b;
}

int main() {
    try {
        std::cout << "결과: " << divide(10, 2) << std::endl; // 정상 출력
        std::cout << "결과: " << divide(10, 0) << std::endl; // 예외 발생
    } catch (const std::runtime_error& e) {
        std::cerr << "오류 발생: " << e.what() << std::endl;
    }
    return 0;
}

🔹 출력 결과

결과: 5
오류 발생: 0으로 나눌 수 없습니다!

🔹 문제점

  • throw로 예외를 발생시키면, 제어 흐름이 복잡해지고 디버깅이 어려워짐.
  • 예외 발생 시 실행이 중단되며, 예외를 catch하지 않으면 프로그램이 비정상 종료될 수 있음.

🔹 (2) C++23의 std::expected를 활용한 예외 처리 대체

C++23에서는 std::expected를 사용하여 예외를 값으로 반환하는 방식을 제공한다.
즉, 예외를 throw하지 않고, 성공(expected) 또는 실패(unexpected) 상태를 값으로 처리 가능하다.

💡 예제: std::expected를 활용한 예외 처리 대체

#include <iostream>
#include <expected>

std::expected<int, std::string> divide(int a, int b) {
    if (b == 0) {
        return std::unexpected("0으로 나눌 수 없습니다!");
    }
    return a / b;
}

int main() {
    auto result1 = divide(10, 2);
    if (result1) {
        std::cout << "결과: " << *result1 << std::endl;
    } else {
        std::cerr << "오류 발생: " << result1.error() << std::endl;
    }

    auto result2 = divide(10, 0);
    if (result2) {
        std::cout << "결과: " << *result2 << std::endl;
    } else {
        std::cerr << "오류 발생: " << result2.error() << std::endl;
    }

    return 0;
}

🔹 출력 결과

결과: 5
오류 발생: 0으로 나눌 수 없습니다!

💡 설명

  • std::expected<T, E>를 사용하면 T(정상값) 또는 E(오류 메시지)를 반환할 수 있음.
  • 예외(throw)를 사용하지 않고, 오류를 값으로 처리하므로 흐름을 쉽게 제어 가능.
  • 오류가 발생하면 std::unexpected("오류 메시지")를 반환하여 에러를 저장.
  • 정상적인 값이면 *result로 접근, 오류가 있으면 result.error()로 메시지 확인.

📌 2. 오류를 값으로 반환하는 방식의 개념 이해

std::expected는 std::optional과 유사하지만, 오류 정보를 포함할 수 있는 것이 차이점이다.

  • std::optional<T> → 값이 있거나 없음을 나타냄 (오류 메시지 X).
  • std::expected<T, E> → 값(T)이 있거나, 오류(E)를 포함할 수 있음.

💡 예제: std::expected vs. std::optional

#include <iostream>
#include <optional>
#include <expected>

std::optional<int> divideOptional(int a, int b) {
    if (b == 0) return std::nullopt;  // 오류 발생 시 null 반환
    return a / b;
}

std::expected<int, std::string> divideExpected(int a, int b) {
    if (b == 0) return std::unexpected("0으로 나눌 수 없습니다!");
    return a / b;
}

int main() {
    auto optResult = divideOptional(10, 0);
    if (optResult) {
        std::cout << "std::optional 결과: " << *optResult << std::endl;
    } else {
        std::cerr << "std::optional 오류 발생!" << std::endl;
    }

    auto expResult = divideExpected(10, 0);
    if (expResult) {
        std::cout << "std::expected 결과: " << *expResult << std::endl;
    } else {
        std::cerr << "std::expected 오류 발생: " << expResult.error() << std::endl;
    }

    return 0;
}

🔹 출력 결과

std::optional 오류 발생!
std::expected 오류 발생: 0으로 나눌 수 없습니다!

💡 설명

  • std::optional은 오류 메시지를 제공하지 않음.
  • std::expected는 오류 내용을 저장하여 사용자에게 더 많은 정보를 제공 가능.

📌 3. 기본적인 사용 예제 소개

🔹 (1) 파일 열기 예제 (std::expected 활용)

💡 예제: 파일이 존재하는지 확인하고 처리

#include <iostream>
#include <fstream>
#include <expected>

std::expected<std::ifstream, std::string> openFile(const std::string& filename) {
    std::ifstream file(filename);
    if (!file) {
        return std::unexpected("파일을 열 수 없습니다: " + filename);
    }
    return std::move(file);
}

int main() {
    auto file = openFile("test.txt");

    if (file) {
        std::cout << "파일을 성공적으로 열었습니다!" << std::endl;
    } else {
        std::cerr << "오류 발생: " << file.error() << std::endl;
    }

    return 0;
}

🔹 출력 결과 (파일이 없을 경우)

오류 발생: 파일을 열 수 없습니다: test.txt

💡 설명

  • 파일이 정상적으로 열리면 std::ifstream을 반환.
  • 파일이 없으면 std::unexpected("오류 메시지")를 반환하여 예외 없이 오류 처리.

📌 4. 정리

개념 설명
std::expected<T, E> 성공(T) 또는 오류(E)를 반환하는 새로운 C++23 기능
예외 (try-catch) 대체 throw 대신 std::expected를 사용하여 오류를 값으로 반환
오류 메시지 포함 가능 std::optional과 다르게 오류 메시지를 저장할 수 있음
예외 안전성 강화 std::expected를 활용하면 흐름을 쉽게 제어 가능
파일 처리 및 연산에 활용 가능 안전한 파일 열기 및 연산 수행 시 유용